fixes for the absolute positioning bug
This commit is contained in:
@@ -1083,7 +1083,7 @@ end
|
|||||||
--- If called before FlexLove.init(), the element creation will be automatically queued and executed after initialization
|
--- If called before FlexLove.init(), the element creation will be automatically queued and executed after initialization
|
||||||
---@param props ElementProps
|
---@param props ElementProps
|
||||||
---@param callback? function Optional callback function(element) that will be called with the created element (useful when queued)
|
---@param callback? function Optional callback function(element) that will be called with the created element (useful when queued)
|
||||||
---@return Element|nil element Returns element if initialized, nil if queued for later creation
|
---@return Element -- Returns element if initialized, nil if queued for later creation
|
||||||
function flexlove.new(props, callback)
|
function flexlove.new(props, callback)
|
||||||
props = props or {}
|
props = props or {}
|
||||||
|
|
||||||
@@ -1102,8 +1102,7 @@ function flexlove.new(props, callback)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
return nil
|
||||||
return nil -- Element will be created later
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Determine effective mode: props.mode takes precedence over global mode
|
-- Determine effective mode: props.mode takes precedence over global mode
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
package = "flexlove"
|
|
||||||
version = "0.7.1-1"
|
|
||||||
|
|
||||||
source = {
|
|
||||||
url = "git+https://github.com/mikefreno/FlexLove.git",
|
|
||||||
tag = "v0.7.1",
|
|
||||||
}
|
|
||||||
|
|
||||||
description = {
|
|
||||||
summary = "A comprehensive UI library providing flexbox/grid layouts, theming, animations, and event handling for LÖVE2D games",
|
|
||||||
detailed = [[
|
|
||||||
FlexLöve is a lightweight, flexible GUI library for LÖVE2D that implements a
|
|
||||||
flexbox-based layout system. The goals of this project are two-fold: first,
|
|
||||||
anyone with basic CSS knowledge should be able to use this library with minimal
|
|
||||||
learning curve. Second, this library should take you from early prototyping to
|
|
||||||
production.
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- Flexbox and Grid Layout systems
|
|
||||||
- Modern theming with 9-patch support
|
|
||||||
- Animations and transitions
|
|
||||||
- Image rendering with CSS-like object-fit
|
|
||||||
- Touch events and gesture recognition
|
|
||||||
- Text input with rich editing features
|
|
||||||
- Responsive design with viewport units
|
|
||||||
- Both immediate and retained rendering modes
|
|
||||||
|
|
||||||
Going this route, you will need to link the luarocks path to your project:
|
|
||||||
(for mac/linux)
|
|
||||||
```lua
|
|
||||||
package.path = package.path .. ";/Users/<username>/.luarocks/share/lua/<version>/?.lua"
|
|
||||||
package.path = package.path .. ";/Users/<username>/.luarocks/share/lua/<version>/?/init.lua"
|
|
||||||
package.cpath = package.cpath .. ";/Users/<username>/.luarocks/lib/lua/<version>/?.so"
|
|
||||||
```
|
|
||||||
]],
|
|
||||||
homepage = "https://mikefreno.github.io/FlexLove/",
|
|
||||||
license = "MIT",
|
|
||||||
maintainer = "Mike Freno",
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies = {
|
|
||||||
"lua >= 5.1",
|
|
||||||
"luautf8 >= 0.1.3",
|
|
||||||
}
|
|
||||||
|
|
||||||
build = {
|
|
||||||
type = "builtin",
|
|
||||||
modules = {
|
|
||||||
["FlexLove"] = "FlexLove.lua",
|
|
||||||
["FlexLove.modules.Animation"] = "modules/Animation.lua",
|
|
||||||
["FlexLove.modules.Blur"] = "modules/Blur.lua",
|
|
||||||
["FlexLove.modules.Calc"] = "modules/Calc.lua",
|
|
||||||
["FlexLove.modules.Color"] = "modules/Color.lua",
|
|
||||||
["FlexLove.modules.Context"] = "modules/Context.lua",
|
|
||||||
["FlexLove.modules.Element"] = "modules/Element.lua",
|
|
||||||
["FlexLove.modules.ErrorHandler"] = "modules/ErrorHandler.lua",
|
|
||||||
["FlexLove.modules.EventHandler"] = "modules/EventHandler.lua",
|
|
||||||
["FlexLove.modules.FFI"] = "modules/FFI.lua",
|
|
||||||
["FlexLove.modules.GestureRecognizer"] = "modules/GestureRecognizer.lua",
|
|
||||||
["FlexLove.modules.Grid"] = "modules/Grid.lua",
|
|
||||||
["FlexLove.modules.ImageCache"] = "modules/ImageCache.lua",
|
|
||||||
["FlexLove.modules.ImageRenderer"] = "modules/ImageRenderer.lua",
|
|
||||||
["FlexLove.modules.ImageScaler"] = "modules/ImageScaler.lua",
|
|
||||||
["FlexLove.modules.InputEvent"] = "modules/InputEvent.lua",
|
|
||||||
["FlexLove.modules.LayoutEngine"] = "modules/LayoutEngine.lua",
|
|
||||||
["FlexLove.modules.MemoryScanner"] = "modules/MemoryScanner.lua",
|
|
||||||
["FlexLove.modules.ModuleLoader"] = "modules/ModuleLoader.lua",
|
|
||||||
["FlexLove.modules.NinePatch"] = "modules/NinePatch.lua",
|
|
||||||
["FlexLove.modules.Performance"] = "modules/Performance.lua",
|
|
||||||
["FlexLove.modules.Renderer"] = "modules/Renderer.lua",
|
|
||||||
["FlexLove.modules.RoundedRect"] = "modules/RoundedRect.lua",
|
|
||||||
["FlexLove.modules.ScrollManager"] = "modules/ScrollManager.lua",
|
|
||||||
["FlexLove.modules.StateManager"] = "modules/StateManager.lua",
|
|
||||||
["FlexLove.modules.TextEditor"] = "modules/TextEditor.lua",
|
|
||||||
["FlexLove.modules.Theme"] = "modules/Theme.lua",
|
|
||||||
["FlexLove.modules.types"] = "modules/types.lua",
|
|
||||||
["FlexLove.modules.Units"] = "modules/Units.lua",
|
|
||||||
["FlexLove.modules.UTF8"] = "modules/UTF8.lua",
|
|
||||||
["FlexLove.modules.utils"] = "modules/utils.lua",
|
|
||||||
},
|
|
||||||
--copy_directories = {
|
|
||||||
--"docs",
|
|
||||||
--"examples",
|
|
||||||
--},
|
|
||||||
}
|
|
||||||
@@ -1308,6 +1308,91 @@ function Element.new(props)
|
|||||||
self._originalPositioning = nil -- No explicit positioning
|
self._originalPositioning = nil -- No explicit positioning
|
||||||
self._explicitlyAbsolute = false
|
self._explicitlyAbsolute = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Handle positioning properties for elements without parent
|
||||||
|
-- Handle top positioning with units
|
||||||
|
if props.top then
|
||||||
|
local isCalc = Element._Calc and Element._Calc.isCalc(props.top)
|
||||||
|
if type(props.top) == "string" or isCalc then
|
||||||
|
local value, unit = Element._Units.parse(props.top)
|
||||||
|
self.units.top = { value = value, unit = unit }
|
||||||
|
local resolvedTop = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
|
||||||
|
if type(resolvedTop) ~= "number" then
|
||||||
|
Element._ErrorHandler:warn("Element", "LAY_003", {
|
||||||
|
issue = "top resolution returned non-number value",
|
||||||
|
type = type(resolvedTop),
|
||||||
|
})
|
||||||
|
resolvedTop = 0
|
||||||
|
end
|
||||||
|
self.top = resolvedTop
|
||||||
|
else
|
||||||
|
self.top = props.top
|
||||||
|
self.units.top = { value = props.top, unit = "px" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle right positioning with units
|
||||||
|
if props.right then
|
||||||
|
local isCalc = Element._Calc and Element._Calc.isCalc(props.right)
|
||||||
|
if type(props.right) == "string" or isCalc then
|
||||||
|
local value, unit = Element._Units.parse(props.right)
|
||||||
|
self.units.right = { value = value, unit = unit }
|
||||||
|
local resolvedRight = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
|
||||||
|
if type(resolvedRight) ~= "number" then
|
||||||
|
Element._ErrorHandler:warn("Element", "LAY_003", {
|
||||||
|
issue = "right resolution returned non-number value",
|
||||||
|
type = type(resolvedRight),
|
||||||
|
})
|
||||||
|
resolvedRight = 0
|
||||||
|
end
|
||||||
|
self.right = resolvedRight
|
||||||
|
else
|
||||||
|
self.right = props.right
|
||||||
|
self.units.right = { value = props.right, unit = "px" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle bottom positioning with units
|
||||||
|
if props.bottom then
|
||||||
|
local isCalc = Element._Calc and Element._Calc.isCalc(props.bottom)
|
||||||
|
if type(props.bottom) == "string" or isCalc then
|
||||||
|
local value, unit = Element._Units.parse(props.bottom)
|
||||||
|
self.units.bottom = { value = value, unit = unit }
|
||||||
|
local resolvedBottom = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
|
||||||
|
if type(resolvedBottom) ~= "number" then
|
||||||
|
Element._ErrorHandler:warn("Element", "LAY_003", {
|
||||||
|
issue = "bottom resolution returned non-number value",
|
||||||
|
type = type(resolvedBottom),
|
||||||
|
})
|
||||||
|
resolvedBottom = 0
|
||||||
|
end
|
||||||
|
self.bottom = resolvedBottom
|
||||||
|
else
|
||||||
|
self.bottom = props.bottom
|
||||||
|
self.units.bottom = { value = props.bottom, unit = "px" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle left positioning with units
|
||||||
|
if props.left then
|
||||||
|
local isCalc = Element._Calc and Element._Calc.isCalc(props.left)
|
||||||
|
if type(props.left) == "string" or isCalc then
|
||||||
|
local value, unit = Element._Units.parse(props.left)
|
||||||
|
self.units.left = { value = value, unit = unit }
|
||||||
|
local resolvedLeft = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
|
||||||
|
if type(resolvedLeft) ~= "number" then
|
||||||
|
Element._ErrorHandler:warn("Element", "LAY_003", {
|
||||||
|
issue = "left resolution returned non-number value",
|
||||||
|
type = type(resolvedLeft),
|
||||||
|
})
|
||||||
|
resolvedLeft = 0
|
||||||
|
end
|
||||||
|
self.left = resolvedLeft
|
||||||
|
else
|
||||||
|
self.left = props.left
|
||||||
|
self.units.left = { value = props.left, unit = "px" }
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- Set positioning first and track if explicitly set
|
-- Set positioning first and track if explicitly set
|
||||||
self._originalPositioning = props.positioning -- Track original intent
|
self._originalPositioning = props.positioning -- Track original intent
|
||||||
@@ -1468,106 +1553,94 @@ function Element.new(props)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Handle positioning properties BEFORE adding to parent (so they're available during layout)
|
||||||
|
-- Handle top positioning with units
|
||||||
|
if props.top then
|
||||||
|
local isCalc = Element._Calc and Element._Calc.isCalc(props.top)
|
||||||
|
if type(props.top) == "string" or isCalc then
|
||||||
|
local value, unit = Element._Units.parse(props.top)
|
||||||
|
self.units.top = { value = value, unit = unit }
|
||||||
|
local resolvedTop = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
|
||||||
|
if type(resolvedTop) ~= "number" then
|
||||||
|
Element._ErrorHandler:warn("Element", "LAY_003", {
|
||||||
|
issue = "top resolution returned non-number value",
|
||||||
|
type = type(resolvedTop),
|
||||||
|
})
|
||||||
|
resolvedTop = 0
|
||||||
|
end
|
||||||
|
self.top = resolvedTop
|
||||||
|
else
|
||||||
|
self.top = props.top
|
||||||
|
self.units.top = { value = props.top, unit = "px" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle right positioning with units
|
||||||
|
if props.right then
|
||||||
|
local isCalc = Element._Calc and Element._Calc.isCalc(props.right)
|
||||||
|
if type(props.right) == "string" or isCalc then
|
||||||
|
local value, unit = Element._Units.parse(props.right)
|
||||||
|
self.units.right = { value = value, unit = unit }
|
||||||
|
local resolvedRight = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
|
||||||
|
if type(resolvedRight) ~= "number" then
|
||||||
|
Element._ErrorHandler:warn("Element", "LAY_003", {
|
||||||
|
issue = "right resolution returned non-number value",
|
||||||
|
type = type(resolvedRight),
|
||||||
|
})
|
||||||
|
resolvedRight = 0
|
||||||
|
end
|
||||||
|
self.right = resolvedRight
|
||||||
|
else
|
||||||
|
self.right = props.right
|
||||||
|
self.units.right = { value = props.right, unit = "px" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle bottom positioning with units
|
||||||
|
if props.bottom then
|
||||||
|
local isCalc = Element._Calc and Element._Calc.isCalc(props.bottom)
|
||||||
|
if type(props.bottom) == "string" or isCalc then
|
||||||
|
local value, unit = Element._Units.parse(props.bottom)
|
||||||
|
self.units.bottom = { value = value, unit = unit }
|
||||||
|
local resolvedBottom = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
|
||||||
|
if type(resolvedBottom) ~= "number" then
|
||||||
|
Element._ErrorHandler:warn("Element", "LAY_003", {
|
||||||
|
issue = "bottom resolution returned non-number value",
|
||||||
|
type = type(resolvedBottom),
|
||||||
|
})
|
||||||
|
resolvedBottom = 0
|
||||||
|
end
|
||||||
|
self.bottom = resolvedBottom
|
||||||
|
else
|
||||||
|
self.bottom = props.bottom
|
||||||
|
self.units.bottom = { value = props.bottom, unit = "px" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle left positioning with units
|
||||||
|
if props.left then
|
||||||
|
local isCalc = Element._Calc and Element._Calc.isCalc(props.left)
|
||||||
|
if type(props.left) == "string" or isCalc then
|
||||||
|
local value, unit = Element._Units.parse(props.left)
|
||||||
|
self.units.left = { value = value, unit = unit }
|
||||||
|
local resolvedLeft = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
|
||||||
|
if type(resolvedLeft) ~= "number" then
|
||||||
|
Element._ErrorHandler:warn("Element", "LAY_003", {
|
||||||
|
issue = "left resolution returned non-number value",
|
||||||
|
type = type(resolvedLeft),
|
||||||
|
})
|
||||||
|
resolvedLeft = 0
|
||||||
|
end
|
||||||
|
self.left = resolvedLeft
|
||||||
|
else
|
||||||
|
self.left = props.left
|
||||||
|
self.units.left = { value = props.left, unit = "px" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
props.parent:addChild(self)
|
props.parent:addChild(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle positioning properties for ALL elements (with or without parent)
|
|
||||||
-- Handle top positioning with units
|
|
||||||
if props.top then
|
|
||||||
local isCalc = Element._Calc and Element._Calc.isCalc(props.top)
|
|
||||||
if type(props.top) == "string" or isCalc then
|
|
||||||
local value, unit = Element._Units.parse(props.top)
|
|
||||||
self.units.top = { value = value, unit = unit }
|
|
||||||
local resolvedTop = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
|
|
||||||
if type(resolvedTop) ~= "number" then
|
|
||||||
Element._ErrorHandler:warn("Element", "LAY_003", {
|
|
||||||
issue = "top resolution returned non-number value",
|
|
||||||
type = type(resolvedTop),
|
|
||||||
})
|
|
||||||
resolvedTop = 0
|
|
||||||
end
|
|
||||||
self.top = resolvedTop
|
|
||||||
else
|
|
||||||
self.top = props.top
|
|
||||||
self.units.top = { value = props.top, unit = "px" }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.top = nil
|
|
||||||
self.units.top = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Handle right positioning with units
|
|
||||||
if props.right then
|
|
||||||
local isCalc = Element._Calc and Element._Calc.isCalc(props.right)
|
|
||||||
if type(props.right) == "string" or isCalc then
|
|
||||||
local value, unit = Element._Units.parse(props.right)
|
|
||||||
self.units.right = { value = value, unit = unit }
|
|
||||||
local resolvedRight = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
|
|
||||||
if type(resolvedRight) ~= "number" then
|
|
||||||
Element._ErrorHandler:warn("Element", "LAY_003", {
|
|
||||||
issue = "right resolution returned non-number value",
|
|
||||||
type = type(resolvedRight),
|
|
||||||
})
|
|
||||||
resolvedRight = 0
|
|
||||||
end
|
|
||||||
self.right = resolvedRight
|
|
||||||
else
|
|
||||||
self.right = props.right
|
|
||||||
self.units.right = { value = props.right, unit = "px" }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.right = nil
|
|
||||||
self.units.right = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Handle bottom positioning with units
|
|
||||||
if props.bottom then
|
|
||||||
local isCalc = Element._Calc and Element._Calc.isCalc(props.bottom)
|
|
||||||
if type(props.bottom) == "string" or isCalc then
|
|
||||||
local value, unit = Element._Units.parse(props.bottom)
|
|
||||||
self.units.bottom = { value = value, unit = unit }
|
|
||||||
local resolvedBottom = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
|
|
||||||
if type(resolvedBottom) ~= "number" then
|
|
||||||
Element._ErrorHandler:warn("Element", "LAY_003", {
|
|
||||||
issue = "bottom resolution returned non-number value",
|
|
||||||
type = type(resolvedBottom),
|
|
||||||
})
|
|
||||||
resolvedBottom = 0
|
|
||||||
end
|
|
||||||
self.bottom = resolvedBottom
|
|
||||||
else
|
|
||||||
self.bottom = props.bottom
|
|
||||||
self.units.bottom = { value = props.bottom, unit = "px" }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.bottom = nil
|
|
||||||
self.units.bottom = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Handle left positioning with units
|
|
||||||
if props.left then
|
|
||||||
local isCalc = Element._Calc and Element._Calc.isCalc(props.left)
|
|
||||||
if type(props.left) == "string" or isCalc then
|
|
||||||
local value, unit = Element._Units.parse(props.left)
|
|
||||||
self.units.left = { value = value, unit = unit }
|
|
||||||
local resolvedLeft = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
|
|
||||||
if type(resolvedLeft) ~= "number" then
|
|
||||||
Element._ErrorHandler:warn("Element", "LAY_003", {
|
|
||||||
issue = "left resolution returned non-number value",
|
|
||||||
type = type(resolvedLeft),
|
|
||||||
})
|
|
||||||
resolvedLeft = 0
|
|
||||||
end
|
|
||||||
self.left = resolvedLeft
|
|
||||||
else
|
|
||||||
self.left = props.left
|
|
||||||
self.units.left = { value = props.left, unit = "px" }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.left = nil
|
|
||||||
self.units.left = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.positioning == Element._utils.enums.Positioning.FLEX then
|
if self.positioning == Element._utils.enums.Positioning.FLEX then
|
||||||
-- Validate enum properties
|
-- Validate enum properties
|
||||||
if props.flexDirection then
|
if props.flexDirection then
|
||||||
@@ -2135,6 +2208,9 @@ function Element:addChild(child)
|
|||||||
|
|
||||||
table.insert(self.children, child)
|
table.insert(self.children, child)
|
||||||
|
|
||||||
|
-- Mark parent as having dirty children to trigger layout recalculation
|
||||||
|
self._childrenDirty = true
|
||||||
|
|
||||||
-- Only recalculate auto-sizing if the child participates in layout
|
-- Only recalculate auto-sizing if the child participates in layout
|
||||||
-- (CSS: absolutely positioned children don't affect parent auto-sizing)
|
-- (CSS: absolutely positioned children don't affect parent auto-sizing)
|
||||||
if not child._explicitlyAbsolute then
|
if not child._explicitlyAbsolute then
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ end
|
|||||||
--- Apply CSS positioning offsets (top, right, bottom, left) to a child element
|
--- Apply CSS positioning offsets (top, right, bottom, left) to a child element
|
||||||
---@param child Element The element to apply offsets to
|
---@param child Element The element to apply offsets to
|
||||||
function LayoutEngine:applyPositioningOffsets(child)
|
function LayoutEngine:applyPositioningOffsets(child)
|
||||||
|
|
||||||
|
|
||||||
if not child then
|
if not child then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -139,7 +141,7 @@ function LayoutEngine:applyPositioningOffsets(child)
|
|||||||
or child.positioning == self._Positioning.GRID
|
or child.positioning == self._Positioning.GRID
|
||||||
or (child.positioning == self._Positioning.ABSOLUTE and not child._explicitlyAbsolute)
|
or (child.positioning == self._Positioning.ABSOLUTE and not child._explicitlyAbsolute)
|
||||||
|
|
||||||
if not isFlexChild then
|
if not isFlexChild and child._explicitlyAbsolute then
|
||||||
-- Apply absolute positioning for explicitly absolute children
|
-- Apply absolute positioning for explicitly absolute children
|
||||||
-- Apply top offset (distance from parent's content box top edge)
|
-- Apply top offset (distance from parent's content box top edge)
|
||||||
if child.top then
|
if child.top then
|
||||||
@@ -224,6 +226,7 @@ end
|
|||||||
|
|
||||||
--- Layout children within this element according to positioning mode
|
--- Layout children within this element according to positioning mode
|
||||||
function LayoutEngine:layoutChildren()
|
function LayoutEngine:layoutChildren()
|
||||||
|
|
||||||
-- Start performance timing first (before any early returns)
|
-- Start performance timing first (before any early returns)
|
||||||
local timerName = nil
|
local timerName = nil
|
||||||
if LayoutEngine._Performance and LayoutEngine._Performance.enabled and self.element then
|
if LayoutEngine._Performance and LayoutEngine._Performance.enabled and self.element then
|
||||||
@@ -250,9 +253,23 @@ function LayoutEngine:layoutChildren()
|
|||||||
if self.positioning == self._Positioning.ABSOLUTE or self.positioning == self._Positioning.RELATIVE then
|
if self.positioning == self._Positioning.ABSOLUTE or self.positioning == self._Positioning.RELATIVE then
|
||||||
-- Absolute/Relative positioned containers don't layout their children according to flex rules,
|
-- Absolute/Relative positioned containers don't layout their children according to flex rules,
|
||||||
-- but they should still apply CSS positioning offsets to their children
|
-- but they should still apply CSS positioning offsets to their children
|
||||||
|
local baseX = (self.element.x or 0) + self.element.padding.left
|
||||||
|
local baseY = (self.element.y or 0) + self.element.padding.top
|
||||||
|
|
||||||
for _, child in ipairs(self.element.children) do
|
for _, child in ipairs(self.element.children) do
|
||||||
|
-- Apply CSS positioning offsets to children with absolute positioning
|
||||||
if child.top or child.right or child.bottom or child.left then
|
if child.top or child.right or child.bottom or child.left then
|
||||||
self:applyPositioningOffsets(child)
|
self:applyPositioningOffsets(child)
|
||||||
|
elseif child.positioning == self._Positioning.RELATIVE then
|
||||||
|
-- Reposition relative children to match parent's new position
|
||||||
|
-- This is needed when the parent (absolute container) moves after children are created
|
||||||
|
child.x = baseX
|
||||||
|
child.y = baseY
|
||||||
|
|
||||||
|
-- If child has children, recursively layout them
|
||||||
|
if #child.children > 0 then
|
||||||
|
child:layoutChildren()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -316,81 +333,45 @@ function LayoutEngine:layoutChildren()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- CSS-compliant behavior: absolutely positioned elements are completely removed from normal flow
|
||||||
|
-- They do NOT reserve space or affect flex layout calculations at all
|
||||||
|
|
||||||
|
-- If no flex children, skip flex layout but still position absolute children
|
||||||
if #flexChildren == 0 then
|
if #flexChildren == 0 then
|
||||||
return
|
-- Position absolutely positioned children even when there are no flex children
|
||||||
end
|
for i, child in ipairs(self.element.children) do
|
||||||
|
if child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute then
|
||||||
-- Calculate space reserved by absolutely positioned siblings with explicit positioning
|
self:applyPositioningOffsets(child)
|
||||||
local reservedMainStart = 0 -- Space reserved at the start of main axis (left for horizontal, top for vertical)
|
|
||||||
local reservedMainEnd = 0 -- Space reserved at the end of main axis (right for horizontal, bottom for vertical)
|
-- If child has children, layout them after position change
|
||||||
local reservedCrossStart = 0 -- Space reserved at the start of cross axis (top for horizontal, left for vertical)
|
if #child.children > 0 then
|
||||||
local reservedCrossEnd = 0 -- Space reserved at the end of cross axis (bottom for horizontal, right for vertical)
|
child:layoutChildren()
|
||||||
|
|
||||||
for _, child in ipairs(self.element.children) do
|
|
||||||
-- Only consider absolutely positioned children with explicit positioning
|
|
||||||
if child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute then
|
|
||||||
-- BORDER-BOX MODEL: Use border-box dimensions for space calculations
|
|
||||||
local childBorderBoxWidth = child:getBorderBoxWidth()
|
|
||||||
local childBorderBoxHeight = child:getBorderBoxHeight()
|
|
||||||
|
|
||||||
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
|
||||||
-- Horizontal layout: main axis is X, cross axis is Y
|
|
||||||
-- Check for left positioning (reserves space at main axis start)
|
|
||||||
if child.left then
|
|
||||||
local spaceNeeded = child.left + childBorderBoxWidth
|
|
||||||
reservedMainStart = math.max(reservedMainStart, spaceNeeded)
|
|
||||||
end
|
|
||||||
-- Check for right positioning (reserves space at main axis end)
|
|
||||||
if child.right then
|
|
||||||
local spaceNeeded = child.right + childBorderBoxWidth
|
|
||||||
reservedMainEnd = math.max(reservedMainEnd, spaceNeeded)
|
|
||||||
end
|
|
||||||
-- Check for top positioning (reserves space at cross axis start)
|
|
||||||
if child.top then
|
|
||||||
local spaceNeeded = child.top + childBorderBoxHeight
|
|
||||||
reservedCrossStart = math.max(reservedCrossStart, spaceNeeded)
|
|
||||||
end
|
|
||||||
-- Check for bottom positioning (reserves space at cross axis end)
|
|
||||||
if child.bottom then
|
|
||||||
local spaceNeeded = child.bottom + childBorderBoxHeight
|
|
||||||
reservedCrossEnd = math.max(reservedCrossEnd, spaceNeeded)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Vertical layout: main axis is Y, cross axis is X
|
|
||||||
-- Check for top positioning (reserves space at main axis start)
|
|
||||||
if child.top then
|
|
||||||
local spaceNeeded = child.top + childBorderBoxHeight
|
|
||||||
reservedMainStart = math.max(reservedMainStart, spaceNeeded)
|
|
||||||
end
|
|
||||||
-- Check for bottom positioning (reserves space at main axis end)
|
|
||||||
if child.bottom then
|
|
||||||
local spaceNeeded = child.bottom + childBorderBoxHeight
|
|
||||||
reservedMainEnd = math.max(reservedMainEnd, spaceNeeded)
|
|
||||||
end
|
|
||||||
-- Check for left positioning (reserves space at cross axis start)
|
|
||||||
if child.left then
|
|
||||||
local spaceNeeded = child.left + childBorderBoxWidth
|
|
||||||
reservedCrossStart = math.max(reservedCrossStart, spaceNeeded)
|
|
||||||
end
|
|
||||||
-- Check for right positioning (reserves space at cross axis end)
|
|
||||||
if child.right then
|
|
||||||
local spaceNeeded = child.right + childBorderBoxWidth
|
|
||||||
reservedCrossEnd = math.max(reservedCrossEnd, spaceNeeded)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Detect overflow after children positioning
|
||||||
|
if self.element._detectOverflow then
|
||||||
|
self.element:_detectOverflow()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Stop performance timing
|
||||||
|
if timerName and LayoutEngine._Performance then
|
||||||
|
LayoutEngine._Performance:stopTimer(timerName)
|
||||||
|
end
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Calculate available space (accounting for padding and reserved space)
|
-- Calculate available space (accounting for padding only, NOT absolute children)
|
||||||
-- BORDER-BOX MODEL: element.width and element.height are already content dimensions (padding subtracted)
|
-- BORDER-BOX MODEL: element.width and element.height are already content dimensions (padding subtracted)
|
||||||
local availableMainSize = 0
|
local availableMainSize = 0
|
||||||
local availableCrossSize = 0
|
local availableCrossSize = 0
|
||||||
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
||||||
availableMainSize = self.element.width - reservedMainStart - reservedMainEnd
|
availableMainSize = self.element.width
|
||||||
availableCrossSize = self.element.height - reservedCrossStart - reservedCrossEnd
|
availableCrossSize = self.element.height
|
||||||
else
|
else
|
||||||
availableMainSize = self.element.height - reservedMainStart - reservedMainEnd
|
availableMainSize = self.element.height
|
||||||
availableCrossSize = self.element.width - reservedCrossStart - reservedCrossEnd
|
availableCrossSize = self.element.width
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle flex wrap: create lines of children
|
-- Handle flex wrap: create lines of children
|
||||||
@@ -614,9 +595,9 @@ function LayoutEngine:layoutChildren()
|
|||||||
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
||||||
-- Horizontal layout: main axis is X, cross axis is Y
|
-- Horizontal layout: main axis is X, cross axis is Y
|
||||||
-- Position child at border box (x, y represents top-left including padding)
|
-- Position child at border box (x, y represents top-left including padding)
|
||||||
-- Add reservedMainStart and left margin to account for absolutely positioned siblings and margins
|
-- CSS-compliant: absolute children don't affect flex positioning, so no reserved space offset
|
||||||
local childMarginLeft = childMargin.left
|
local childMarginLeft = childMargin.left
|
||||||
child.x = elementX + elementPaddingLeft + reservedMainStart + currentMainPos + childMarginLeft
|
child.x = elementX + elementPaddingLeft + currentMainPos + childMarginLeft
|
||||||
|
|
||||||
-- BORDER-BOX MODEL: Use border-box dimensions for alignment calculations
|
-- BORDER-BOX MODEL: Use border-box dimensions for alignment calculations
|
||||||
local childBorderBoxHeight = child:getBorderBoxHeight()
|
local childBorderBoxHeight = child:getBorderBoxHeight()
|
||||||
@@ -625,16 +606,15 @@ function LayoutEngine:layoutChildren()
|
|||||||
local childTotalCrossSize = childBorderBoxHeight + childMarginTop + childMarginBottom
|
local childTotalCrossSize = childBorderBoxHeight + childMarginTop + childMarginBottom
|
||||||
|
|
||||||
if effectiveAlign == alignItems_FLEX_START then
|
if effectiveAlign == alignItems_FLEX_START then
|
||||||
child.y = elementY + elementPaddingTop + reservedCrossStart + currentCrossPos + childMarginTop
|
child.y = elementY + elementPaddingTop + currentCrossPos + childMarginTop
|
||||||
elseif effectiveAlign == alignItems_CENTER then
|
elseif effectiveAlign == alignItems_CENTER then
|
||||||
child.y = elementY
|
child.y = elementY
|
||||||
+ elementPaddingTop
|
+ elementPaddingTop
|
||||||
+ reservedCrossStart
|
|
||||||
+ currentCrossPos
|
+ currentCrossPos
|
||||||
+ ((lineHeight - childTotalCrossSize) / 2)
|
+ ((lineHeight - childTotalCrossSize) / 2)
|
||||||
+ childMarginTop
|
+ childMarginTop
|
||||||
elseif effectiveAlign == alignItems_FLEX_END then
|
elseif effectiveAlign == alignItems_FLEX_END then
|
||||||
child.y = elementY + elementPaddingTop + reservedCrossStart + currentCrossPos + lineHeight - childTotalCrossSize + childMarginTop
|
child.y = elementY + elementPaddingTop + currentCrossPos + lineHeight - childTotalCrossSize + childMarginTop
|
||||||
elseif effectiveAlign == alignItems_STRETCH then
|
elseif effectiveAlign == alignItems_STRETCH then
|
||||||
-- STRETCH: Only apply if height was not explicitly set
|
-- STRETCH: Only apply if height was not explicitly set
|
||||||
if childAutosizing and childAutosizing.height then
|
if childAutosizing and childAutosizing.height then
|
||||||
@@ -643,7 +623,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
child._borderBoxHeight = availableHeight
|
child._borderBoxHeight = availableHeight
|
||||||
child.height = math.max(0, availableHeight - childPadding.top - childPadding.bottom)
|
child.height = math.max(0, availableHeight - childPadding.top - childPadding.bottom)
|
||||||
end
|
end
|
||||||
child.y = elementY + elementPaddingTop + reservedCrossStart + currentCrossPos + childMarginTop
|
child.y = elementY + elementPaddingTop + currentCrossPos + childMarginTop
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply positioning offsets (top, right, bottom, left)
|
-- Apply positioning offsets (top, right, bottom, left)
|
||||||
@@ -659,9 +639,9 @@ function LayoutEngine:layoutChildren()
|
|||||||
else
|
else
|
||||||
-- Vertical layout: main axis is Y, cross axis is X
|
-- Vertical layout: main axis is Y, cross axis is X
|
||||||
-- Position child at border box (x, y represents top-left including padding)
|
-- Position child at border box (x, y represents top-left including padding)
|
||||||
-- Add reservedMainStart and top margin to account for absolutely positioned siblings and margins
|
-- CSS-compliant: absolute children don't affect flex positioning, so no reserved space offset
|
||||||
local childMarginTop = childMargin.top
|
local childMarginTop = childMargin.top
|
||||||
child.y = elementY + elementPaddingTop + reservedMainStart + currentMainPos + childMarginTop
|
child.y = elementY + elementPaddingTop + currentMainPos + childMarginTop
|
||||||
|
|
||||||
-- BORDER-BOX MODEL: Use border-box dimensions for alignment calculations
|
-- BORDER-BOX MODEL: Use border-box dimensions for alignment calculations
|
||||||
local childBorderBoxWidth = child:getBorderBoxWidth()
|
local childBorderBoxWidth = child:getBorderBoxWidth()
|
||||||
@@ -671,16 +651,15 @@ function LayoutEngine:layoutChildren()
|
|||||||
local elementPaddingLeft = elementPadding.left
|
local elementPaddingLeft = elementPadding.left
|
||||||
|
|
||||||
if effectiveAlign == alignItems_FLEX_START then
|
if effectiveAlign == alignItems_FLEX_START then
|
||||||
child.x = elementX + elementPaddingLeft + reservedCrossStart + currentCrossPos + childMarginLeft
|
child.x = elementX + elementPaddingLeft + currentCrossPos + childMarginLeft
|
||||||
elseif effectiveAlign == alignItems_CENTER then
|
elseif effectiveAlign == alignItems_CENTER then
|
||||||
child.x = elementX
|
child.x = elementX
|
||||||
+ elementPaddingLeft
|
+ elementPaddingLeft
|
||||||
+ reservedCrossStart
|
|
||||||
+ currentCrossPos
|
+ currentCrossPos
|
||||||
+ ((lineHeight - childTotalCrossSize) / 2)
|
+ ((lineHeight - childTotalCrossSize) / 2)
|
||||||
+ childMarginLeft
|
+ childMarginLeft
|
||||||
elseif effectiveAlign == alignItems_FLEX_END then
|
elseif effectiveAlign == alignItems_FLEX_END then
|
||||||
child.x = elementX + elementPaddingLeft + reservedCrossStart + currentCrossPos + lineHeight - childTotalCrossSize + childMarginLeft
|
child.x = elementX + elementPaddingLeft + currentCrossPos + lineHeight - childTotalCrossSize + childMarginLeft
|
||||||
elseif effectiveAlign == alignItems_STRETCH then
|
elseif effectiveAlign == alignItems_STRETCH then
|
||||||
-- STRETCH: Only apply if width was not explicitly set
|
-- STRETCH: Only apply if width was not explicitly set
|
||||||
if childAutosizing and childAutosizing.width then
|
if childAutosizing and childAutosizing.width then
|
||||||
@@ -689,7 +668,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
child._borderBoxWidth = availableWidth
|
child._borderBoxWidth = availableWidth
|
||||||
child.width = math.max(0, availableWidth - childPadding.left - childPadding.right)
|
child.width = math.max(0, availableWidth - childPadding.left - childPadding.right)
|
||||||
end
|
end
|
||||||
child.x = elementX + elementPaddingLeft + reservedCrossStart + currentCrossPos + childMarginLeft
|
child.x = elementX + elementPaddingLeft + currentCrossPos + childMarginLeft
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply positioning offsets (top, right, bottom, left)
|
-- Apply positioning offsets (top, right, bottom, left)
|
||||||
@@ -710,7 +689,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Position explicitly absolute children after flex layout
|
-- Position explicitly absolute children after flex layout
|
||||||
for _, child in ipairs(self.element.children) do
|
for i, child in ipairs(self.element.children) do
|
||||||
if child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute then
|
if child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute then
|
||||||
-- Apply positioning offsets (top, right, bottom, left)
|
-- Apply positioning offsets (top, right, bottom, left)
|
||||||
self:applyPositioningOffsets(child)
|
self:applyPositioningOffsets(child)
|
||||||
|
|||||||
267
testing/__tests__/absolute_positioning_test.lua
Normal file
267
testing/__tests__/absolute_positioning_test.lua
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
package.path = package.path .. ";./?.lua;./modules/?.lua"
|
||||||
|
local originalSearchers = package.searchers or package.loaders
|
||||||
|
table.insert(originalSearchers, 2, function(modname)
|
||||||
|
if modname:match("^FlexLove%.modules%.") then
|
||||||
|
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
|
||||||
|
return function()
|
||||||
|
return require("modules." .. moduleName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
require("testing.loveStub")
|
||||||
|
local luaunit = require("testing.luaunit")
|
||||||
|
|
||||||
|
-- Load FlexLove
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
|
||||||
|
TestAbsolutePositioning = {}
|
||||||
|
|
||||||
|
function TestAbsolutePositioning:setUp()
|
||||||
|
FlexLove.init()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestAbsolutePositioning:testAbsoluteBottomRightInFlexParent()
|
||||||
|
-- Create a flex parent
|
||||||
|
local parent = FlexLove.new({
|
||||||
|
positioning = "flex",
|
||||||
|
width = 400,
|
||||||
|
height = 400,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Create an absolutely positioned child with bottom and right offsets
|
||||||
|
local child = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
bottom = 0,
|
||||||
|
right = 0,
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Child should be positioned at bottom-right
|
||||||
|
luaunit.assertEquals(child.x, 300, "Child x should be 300 (400 - 100)")
|
||||||
|
luaunit.assertEquals(child.y, 300, "Child y should be 300 (400 - 100)")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestAbsolutePositioning:testAbsoluteTopLeftInFlexParent()
|
||||||
|
-- Create a flex parent
|
||||||
|
local parent = FlexLove.new({
|
||||||
|
positioning = "flex",
|
||||||
|
width = 400,
|
||||||
|
height = 400,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Create an absolutely positioned child with top and left offsets
|
||||||
|
local child = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
top = 10,
|
||||||
|
left = 10,
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Child should be positioned at top-left with 10px offset
|
||||||
|
luaunit.assertEquals(child.x, 10, "Child x should be 10")
|
||||||
|
luaunit.assertEquals(child.y, 10, "Child y should be 10")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestAbsolutePositioning:testAbsoluteWithPaddingParent()
|
||||||
|
-- Create a flex parent with padding
|
||||||
|
local parent = FlexLove.new({
|
||||||
|
positioning = "flex",
|
||||||
|
width = 400,
|
||||||
|
height = 400,
|
||||||
|
padding = 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Create an absolutely positioned child
|
||||||
|
local child = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
bottom = 0,
|
||||||
|
right = 0,
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Absolute positioning is relative to parent's padding box
|
||||||
|
-- Parent content box: 400 - 20 (left padding) - 20 (right padding) = 360
|
||||||
|
-- Child x: parent.x (0) + padding.left (20) + content.width (360) - right (0) - child.width (100) = 280
|
||||||
|
luaunit.assertEquals(child.x, 280, "Child x should account for parent padding")
|
||||||
|
luaunit.assertEquals(child.y, 280, "Child y should account for parent padding")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestAbsolutePositioning:testAbsoluteDoesNotAffectFlexLayout()
|
||||||
|
-- Create a flex parent
|
||||||
|
local parent = FlexLove.new({
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "horizontal",
|
||||||
|
width = 400,
|
||||||
|
height = 400,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Add flex children
|
||||||
|
local flexChild1 = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
local flexChild2 = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Add absolutely positioned child
|
||||||
|
local absChild = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
top = 0,
|
||||||
|
left = 0,
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Flex children should be positioned normally (absolute child doesn't affect layout)
|
||||||
|
luaunit.assertEquals(flexChild1.x, 0, "First flex child at x=0")
|
||||||
|
luaunit.assertEquals(flexChild2.x, 100, "Second flex child at x=100")
|
||||||
|
|
||||||
|
-- Absolute child should be at top-left
|
||||||
|
luaunit.assertEquals(absChild.x, 0, "Absolute child at x=0")
|
||||||
|
luaunit.assertEquals(absChild.y, 0, "Absolute child at y=0")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestAbsolutePositioning:testMultipleAbsoluteChildren()
|
||||||
|
-- Create a flex parent
|
||||||
|
local parent = FlexLove.new({
|
||||||
|
positioning = "flex",
|
||||||
|
width = 400,
|
||||||
|
height = 400,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Create multiple absolutely positioned children
|
||||||
|
local topLeft = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
top = 0,
|
||||||
|
left = 0,
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
local topRight = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
top = 0,
|
||||||
|
right = 0,
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
local bottomLeft = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
bottom = 0,
|
||||||
|
left = 0,
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
local bottomRight = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
bottom = 0,
|
||||||
|
right = 0,
|
||||||
|
width = 50,
|
||||||
|
height = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Verify positions
|
||||||
|
luaunit.assertEquals(topLeft.x, 0, "Top-left x")
|
||||||
|
luaunit.assertEquals(topLeft.y, 0, "Top-left y")
|
||||||
|
|
||||||
|
luaunit.assertEquals(topRight.x, 350, "Top-right x")
|
||||||
|
luaunit.assertEquals(topRight.y, 0, "Top-right y")
|
||||||
|
|
||||||
|
luaunit.assertEquals(bottomLeft.x, 0, "Bottom-left x")
|
||||||
|
luaunit.assertEquals(bottomLeft.y, 350, "Bottom-left y")
|
||||||
|
|
||||||
|
luaunit.assertEquals(bottomRight.x, 350, "Bottom-right x")
|
||||||
|
luaunit.assertEquals(bottomRight.y, 350, "Bottom-right y")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestAbsolutePositioning:testAbsoluteInImmediateMode()
|
||||||
|
FlexLove.setMode("immediate")
|
||||||
|
|
||||||
|
local parent, child
|
||||||
|
|
||||||
|
local function createUI()
|
||||||
|
parent = FlexLove.new({
|
||||||
|
positioning = "flex",
|
||||||
|
width = 400,
|
||||||
|
height = 400,
|
||||||
|
})
|
||||||
|
|
||||||
|
child = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
bottom = 0,
|
||||||
|
right = 0,
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- First frame
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
createUI()
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
luaunit.assertEquals(child.x, 300, "Frame 1: Child x should be 300")
|
||||||
|
luaunit.assertEquals(child.y, 300, "Frame 1: Child y should be 300")
|
||||||
|
|
||||||
|
-- Second frame (recreate UI)
|
||||||
|
FlexLove.beginFrame()
|
||||||
|
createUI()
|
||||||
|
FlexLove.endFrame()
|
||||||
|
|
||||||
|
luaunit.assertEquals(child.x, 300, "Frame 2: Child x should be 300")
|
||||||
|
luaunit.assertEquals(child.y, 300, "Frame 2: Child y should be 300")
|
||||||
|
|
||||||
|
FlexLove.setMode("retained")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestAbsolutePositioning:testExplicitlyAbsoluteFlagIsSet()
|
||||||
|
local parent = FlexLove.new({
|
||||||
|
positioning = "flex",
|
||||||
|
width = 400,
|
||||||
|
height = 400,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Child with explicit absolute positioning
|
||||||
|
local absoluteChild = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
positioning = "absolute",
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Child without explicit positioning (participates in flex)
|
||||||
|
local flexChild = FlexLove.new({
|
||||||
|
parent = parent,
|
||||||
|
width = 100,
|
||||||
|
height = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
luaunit.assertEquals(absoluteChild._explicitlyAbsolute, true, "Explicitly absolute child should have _explicitlyAbsolute = true")
|
||||||
|
luaunit.assertEquals(absoluteChild._originalPositioning, "absolute", "Absolute child should have _originalPositioning = 'absolute'")
|
||||||
|
|
||||||
|
luaunit.assertEquals(flexChild._explicitlyAbsolute, false, "Flex child should have _explicitlyAbsolute = false")
|
||||||
|
luaunit.assertEquals(flexChild._originalPositioning, nil, "Flex child should have _originalPositioning = nil")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not _G.RUNNING_ALL_TESTS then
|
||||||
|
os.exit(luaunit.LuaUnit.run())
|
||||||
|
end
|
||||||
@@ -36,6 +36,7 @@ end
|
|||||||
local luaunit = require("testing.luaunit")
|
local luaunit = require("testing.luaunit")
|
||||||
|
|
||||||
local testFiles = {
|
local testFiles = {
|
||||||
|
"testing/__tests__/absolute_positioning_test.lua",
|
||||||
"testing/__tests__/animation_test.lua",
|
"testing/__tests__/animation_test.lua",
|
||||||
"testing/__tests__/blur_test.lua",
|
"testing/__tests__/blur_test.lua",
|
||||||
"testing/__tests__/calc_test.lua",
|
"testing/__tests__/calc_test.lua",
|
||||||
|
|||||||
Reference in New Issue
Block a user