diff --git a/FlexLove.lua b/FlexLove.lua index 3662335..a5f218b 100644 --- a/FlexLove.lua +++ b/FlexLove.lua @@ -40,6 +40,7 @@ enums.TextAlign = { --- @enum Positioning enums.Positioning = { ABSOLUTE = "absolute", + RELATIVE = "relative", FLEX = "flex", } @@ -743,7 +744,7 @@ function Element.new(props) self._originalPositioning = props.positioning self._explicitlyAbsolute = (props.positioning == Positioning.ABSOLUTE) else - self.positioning = Positioning.ABSOLUTE + self.positioning = Positioning.RELATIVE self._originalPositioning = nil -- No explicit positioning self._explicitlyAbsolute = false end @@ -758,13 +759,13 @@ function Element.new(props) self._explicitlyAbsolute = false else -- Default: children in flex containers participate in flex layout - -- children in absolute containers default to absolute + -- children in other containers default to relative if self.parent.positioning == Positioning.FLEX then self.positioning = Positioning.ABSOLUTE -- They are positioned BY flex, not AS flex self._explicitlyAbsolute = false -- Participate in parent's flex layout else - self.positioning = Positioning.ABSOLUTE - self._explicitlyAbsolute = false -- Default for absolute containers + self.positioning = Positioning.RELATIVE + self._explicitlyAbsolute = false -- Default for other containers end end @@ -803,6 +804,44 @@ function Element.new(props) end self.z = props.z or 0 + elseif self.positioning == Positioning.RELATIVE then + -- Relative positioning: position relative to parent's position + local baseX = self.parent.x + local baseY = self.parent.y + + if props.x then + if type(props.x) == "string" then + local value, unit = Units.parse(props.x) + self.units.x = { value = value, unit = unit } + local parentWidth = self.parent.width + local offsetX = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth) + self.x = baseX + offsetX + else + self.x = baseX + props.x + self.units.x = { value = props.x, unit = "px" } + end + else + self.x = baseX + self.units.x = { value = 0, unit = "px" } + end + + if props.y then + if type(props.y) == "string" then + local value, unit = Units.parse(props.y) + self.units.y = { value = value, unit = unit } + local parentHeight = self.parent.height + local offsetY = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight) + self.y = baseY + offsetY + else + self.y = baseY + props.y + self.units.y = { value = props.y, unit = "px" } + end + else + self.y = baseY + self.units.y = { value = 0, unit = "px" } + end + + self.z = props.z or self.parent.z or 0 else -- Children in flex containers start at parent position but will be repositioned by layoutChildren local baseX = self.parent.x @@ -946,8 +985,8 @@ function Element:addChild(child) child.positioning = Positioning.ABSOLUTE -- They are positioned BY flex, not AS flex child._explicitlyAbsolute = false -- Participate in parent's flex layout else - child.positioning = Positioning.ABSOLUTE - child._explicitlyAbsolute = false -- Default for absolute containers + child.positioning = Positioning.RELATIVE + child._explicitlyAbsolute = false -- Default for other containers end end -- If child._originalPositioning is set, it means explicit positioning was provided @@ -1020,7 +1059,10 @@ function Element:layoutChildren() -- Get flex children (children that participate in flex layout) local flexChildren = {} for _, child in ipairs(self.children) do - local isFlexChild = not (child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute) + -- Only flex positioned children or non-explicitly absolute children participate in flex layout + -- Relative positioned children maintain their own positioning + local isFlexChild = not (child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute) + and child.positioning ~= Positioning.RELATIVE if isFlexChild then table.insert(flexChildren, child) end diff --git a/testing/__tests__/01_absolute_positioning_basic_tests.lua b/testing/__tests__/01_absolute_positioning_basic_tests.lua index 48e06af..b49bbc6 100644 --- a/testing/__tests__/01_absolute_positioning_basic_tests.lua +++ b/testing/__tests__/01_absolute_positioning_basic_tests.lua @@ -51,8 +51,8 @@ function TestAbsolutePositioningBasic:testDefaultAbsolutePositioning() h = 100, }) - -- Default should be absolute positioning - luaunit.assertEquals(elem.positioning, Positioning.ABSOLUTE) + -- Default should be relative positioning + luaunit.assertEquals(elem.positioning, Positioning.RELATIVE) luaunit.assertEquals(elem.x, 50) luaunit.assertEquals(elem.y, 75) end diff --git a/testing/__tests__/13_relative_positioning_tests.lua b/testing/__tests__/13_relative_positioning_tests.lua new file mode 100644 index 0000000..01ec8b1 --- /dev/null +++ b/testing/__tests__/13_relative_positioning_tests.lua @@ -0,0 +1,199 @@ +-- Test relative positioning functionality +package.path = package.path .. ";?.lua" +require("testing/loveStub") +local FlexLove = require("FlexLove") +local luaunit = require("testing/luaunit") + +local Gui, enums = FlexLove.GUI, FlexLove.enums +local Color = FlexLove.Color +local Positioning = enums.Positioning + +TestRelativePositioning = {} + +-- Test 1: Basic relative positioning with pixel values +function TestRelativePositioning.testBasicRelativePositioning() + local parent = Gui.new({ + x = 100, + y = 50, + w = 200, + h = 150, + positioning = "relative", + background = Color.new(0.2, 0.2, 0.2, 1.0), + }) + + local child = Gui.new({ + parent = parent, + x = 20, + y = 30, + w = 50, + h = 40, + positioning = "relative", + background = Color.new(0.8, 0.2, 0.2, 1.0) + }) + + -- Child should be positioned relative to parent + luaunit.assertEquals(child.positioning, Positioning.RELATIVE) + luaunit.assertEquals(child.x, 120) -- parent.x (100) + offset (20) + luaunit.assertEquals(child.y, 80) -- parent.y (50) + offset (30) +end + +-- Test 2: Relative positioning with percentage values +function TestRelativePositioning.testRelativePositioningPercentages() + local parent = Gui.new({ + x = 50, + y = 100, + w = 200, + h = 100, + positioning = "relative", + background = Color.new(0.2, 0.2, 0.2, 1.0), + }) + + local child = Gui.new({ + parent = parent, + x = "10%", -- 10% of parent width = 20px + y = "20%", -- 20% of parent height = 20px + w = 30, + h = 20, + positioning = "relative", + background = Color.new(0.8, 0.2, 0.2, 1.0) + }) + + -- Child should be positioned relative to parent with percentage offsets + luaunit.assertEquals(child.positioning, Positioning.RELATIVE) + luaunit.assertEquals(child.x, 70.0) -- parent.x (50) + 10% of width (20) + luaunit.assertEquals(child.y, 120.0) -- parent.y (100) + 20% of height (20) +end + +-- Test 3: Relative positioning with no offset (default to parent position) +function TestRelativePositioning.testRelativePositioningNoOffset() + local parent = Gui.new({ + x = 75, + y = 125, + w = 150, + h = 200, + positioning = "relative", + background = Color.new(0.2, 0.2, 0.2, 1.0), + }) + + local child = Gui.new({ + parent = parent, + w = 40, + h = 30, + positioning = "relative", + background = Color.new(0.2, 0.8, 0.2, 1.0) + }) + + -- Child should be positioned at parent's position with no offset + luaunit.assertEquals(child.positioning, Positioning.RELATIVE) + luaunit.assertEquals(child.x, 75) -- same as parent.x + luaunit.assertEquals(child.y, 125) -- same as parent.y +end + +-- Test 4: Multiple relative positioned children +function TestRelativePositioning.testMultipleRelativeChildren() + local parent = Gui.new({ + x = 200, + y = 300, + w = 100, + h = 100, + positioning = "relative", + background = Color.new(0.2, 0.2, 0.2, 1.0), + }) + + local child1 = Gui.new({ + parent = parent, + x = 10, + y = 15, + w = 20, + h = 20, + positioning = "relative", + background = Color.new(0.8, 0.2, 0.2, 1.0) + }) + + local child2 = Gui.new({ + parent = parent, + x = 30, + y = 45, + w = 25, + h = 25, + positioning = "relative", + background = Color.new(0.2, 0.8, 0.2, 1.0) + }) + + -- Both children should be positioned relative to parent + luaunit.assertEquals(child1.x, 210) -- parent.x (200) + offset (10) + luaunit.assertEquals(child1.y, 315) -- parent.y (300) + offset (15) + + luaunit.assertEquals(child2.x, 230) -- parent.x (200) + offset (30) + luaunit.assertEquals(child2.y, 345) -- parent.y (300) + offset (45) +end + +-- Test 5: Nested relative positioning +function TestRelativePositioning.testNestedRelativePositioning() + local grandparent = Gui.new({ + x = 50, + y = 60, + w = 300, + h = 250, + positioning = "relative", + background = Color.new(0.1, 0.1, 0.1, 1.0), + }) + + local parent = Gui.new({ + parent = grandparent, + x = 25, + y = 35, + w = 200, + h = 150, + positioning = "relative", + background = Color.new(0.3, 0.3, 0.3, 1.0), + }) + + local child = Gui.new({ + parent = parent, + x = 15, + y = 20, + w = 50, + h = 40, + positioning = "relative", + background = Color.new(0.8, 0.8, 0.8, 1.0) + }) + + -- Each level should be positioned relative to its parent + luaunit.assertEquals(parent.x, 75) -- grandparent.x (50) + offset (25) + luaunit.assertEquals(parent.y, 95) -- grandparent.y (60) + offset (35) + + luaunit.assertEquals(child.x, 90) -- parent.x (75) + offset (15) + luaunit.assertEquals(child.y, 115) -- parent.y (95) + offset (20) +end + +-- Test 6: Mixed positioning types (relative child in absolute parent) +function TestRelativePositioning.testMixedPositioning() + local parent = Gui.new({ + x = 100, + y = 200, + w = 180, + h = 120, + positioning = "absolute", + background = Color.new(0.2, 0.2, 0.2, 1.0), + }) + + local child = Gui.new({ + parent = parent, + x = 40, + y = 25, + w = 60, + h = 35, + positioning = "relative", + background = Color.new(0.8, 0.8, 0.2, 1.0) + }) + + -- Relative child should still be positioned relative to absolute parent + luaunit.assertEquals(parent.positioning, Positioning.ABSOLUTE) + luaunit.assertEquals(child.positioning, Positioning.RELATIVE) + luaunit.assertEquals(child.x, 140) -- parent.x (100) + offset (40) + luaunit.assertEquals(child.y, 225) -- parent.y (200) + offset (25) +end + +-- Run all tests +os.exit(luaunit.LuaUnit.run()) \ No newline at end of file diff --git a/testing/__tests__/14_text_scaling_basic_tests.lua b/testing/__tests__/14_text_scaling_basic_tests.lua new file mode 100644 index 0000000..eefeb33 --- /dev/null +++ b/testing/__tests__/14_text_scaling_basic_tests.lua @@ -0,0 +1,124 @@ +-- Test file for basic text scaling functionality +-- This tests simple cases where elements scale text appropriately during resize + +package.path = './testing/?.lua;./?.lua;' .. package.path +local luaunit = require("luaunit") + +-- Mock love module for testing +love = { + graphics = { + getDimensions = function() return 800, 600 end, + newFont = function(size) return { getWidth = function(text) return size * #text end, getHeight = function() return size end } end, + getFont = function() return { getWidth = function(text) return 12 * #text end, getHeight = function() return 12 end } end, + }, + window = { + getMode = function() return 800, 600 end + } +} + +-- Import the FlexLove library +local FlexLove = require("FlexLove") +local Gui = FlexLove.GUI + +-- Test suite for basic text scaling +local BasicTextScalingTests = {} + +function BasicTextScalingTests.testFixedTextSize() + -- Create an element with fixed textSize in pixels + local element = Gui.new({ + id = "testElement", + w = 100, + h = 50, + textSize = 16, -- Fixed size in pixels + text = "Hello World" + }) + + -- Check initial state + luaunit.assertEquals(element.textSize, 16) + + -- Simulate a resize with larger viewport + local newWidth, newHeight = 800, 600 + element:resize(newWidth, newHeight) + + -- Fixed textSize should remain unchanged + luaunit.assertEquals(element.textSize, 16) +end + +function BasicTextScalingTests.testPercentageTextSize() + -- Create an element with percentage textSize + local element = Gui.new({ + id = "testElement", + w = 100, + h = 50, + textSize = "5%", -- Percentage of viewport height + text = "Hello World" + }) + + -- Check initial state + luaunit.assertEquals(element.units.textSize.unit, "%") + + -- Simulate a resize with larger viewport + local newWidth, newHeight = 800, 600 + element:resize(newWidth, newHeight) + + -- Percentage textSize should be recalculated + luaunit.assertEquals(element.textSize, 30) -- 5% of 600 height = 30 +end + +function BasicTextScalingTests.testVwTextSize() + -- Create an element with vw textSize + local element = Gui.new({ + id = "testElement", + w = 100, + h = 50, + textSize = "2vw", -- 2% of viewport width + text = "Hello World" + }) + + -- Check initial state + luaunit.assertEquals(element.units.textSize.unit, "vw") + + -- Simulate a resize with larger viewport + local newWidth, newHeight = 800, 600 + element:resize(newWidth, newHeight) + + -- vw textSize should be recalculated + luaunit.assertEquals(element.textSize, 16) -- 2% of 800 width = 16 +end + +function BasicTextScalingTests.testVhTextSize() + -- Create an element with vh textSize + local element = Gui.new({ + id = "testElement", + w = 100, + h = 50, + textSize = "3vh", -- 3% of viewport height + text = "Hello World" + }) + + -- Check initial state + luaunit.assertEquals(element.units.textSize.unit, "vh") + + -- Simulate a resize with larger viewport + local newWidth, newHeight = 800, 600 + element:resize(newWidth, newHeight) + + -- vh textSize should be recalculated + luaunit.assertEquals(element.textSize, 18) -- 3% of 600 height = 18 +end + +function BasicTextScalingTests.testNoTextSize() + -- Create an element without textSize specified + local element = Gui.new({ + id = "testElement", + w = 100, + h = 50, + text = "Hello World" + }) + + -- Check initial state - should default to some value + luaunit.assertEquals(element.units.textSize.value, nil) + luaunit.assertEquals(element.textSize, 12) -- Default fallback +end + +return BasicTextScalingTests \ No newline at end of file