simplified grid implementation

This commit is contained in:
Michael Freno
2025-10-11 21:05:24 -04:00
parent 2c68260340
commit 3df05d561f
5 changed files with 444 additions and 1153 deletions

View File

@@ -102,38 +102,9 @@ local enums = {
}, },
---@enum FlexWrap ---@enum FlexWrap
FlexWrap = { NOWRAP = "nowrap", WRAP = "wrap", WRAP_REVERSE = "wrap-reverse" }, FlexWrap = { NOWRAP = "nowrap", WRAP = "wrap", WRAP_REVERSE = "wrap-reverse" },
---@enum GridAutoFlow
GridAutoFlow = { ROW = "row", COLUMN = "column", ROW_DENSE = "row dense", COLUMN_DENSE = "column dense" },
---@enum GridJustifyItems
GridJustifyItems = {
STRETCH = "stretch",
START = "start",
END = "end",
CENTER = "center",
},
---@enum GridAlignContent
GridAlignContent = {
STRETCH = "stretch",
START = "start",
END = "end",
CENTER = "center",
SPACE_BETWEEN = "space-between",
SPACE_AROUND = "space-around",
SPACE_EVENLY = "space-evenly",
},
---@enum GridJustifyContent
GridJustifyContent = {
STRETCH = "stretch",
START = "start",
END = "end",
CENTER = "center",
SPACE_BETWEEN = "space-between",
SPACE_AROUND = "space-around",
SPACE_EVENLY = "space-evenly",
},
} }
local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, TextAlign, AlignSelf, JustifySelf, FlexWrap, GridAutoFlow, GridJustifyItems, GridAlignContent, GridJustifyContent = local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, TextAlign, AlignSelf, JustifySelf, FlexWrap =
enums.Positioning, enums.Positioning,
enums.FlexDirection, enums.FlexDirection,
enums.JustifyContent, enums.JustifyContent,
@@ -142,11 +113,7 @@ local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, Text
enums.TextAlign, enums.TextAlign,
enums.AlignSelf, enums.AlignSelf,
enums.JustifySelf, enums.JustifySelf,
enums.FlexWrap, enums.FlexWrap
enums.GridAutoFlow,
enums.GridJustifyItems,
enums.GridAlignContent,
enums.GridJustifyContent
-- ==================== -- ====================
-- Units System -- Units System
@@ -301,423 +268,88 @@ end
-- Grid System -- Grid System
-- ==================== -- ====================
--- Grid track parsing and layout calculations --- Simple grid layout calculations
local Grid = {} local Grid = {}
--- Parse a single track size value --- Layout grid items within a grid container using simple row/column counts
---@param trackSize string|number
---@return table -- { type: "px"|"fr"|"%"|"auto"|"minmax"|"min-content"|"max-content", value: number?, min: number?, max: number? }
function Grid.parseTrackSize(trackSize)
if type(trackSize) == "number" then
return { type = "px", value = trackSize }
end
if type(trackSize) ~= "string" then
return { type = "auto" }
end
-- Handle auto
if trackSize == "auto" then
return { type = "auto" }
end
-- Handle min-content and max-content
if trackSize == "min-content" then
return { type = "min-content" }
end
if trackSize == "max-content" then
return { type = "max-content" }
end
-- Handle fr units
local frValue = trackSize:match("^([%d%.]+)fr$")
if frValue then
return { type = "fr", value = tonumber(frValue) }
end
-- Handle percentage
local percentValue = trackSize:match("^([%d%.]+)%%$")
if percentValue then
return { type = "%", value = tonumber(percentValue) }
end
-- Handle pixel values
local pxValue = trackSize:match("^([%d%.]+)px$")
if pxValue then
return { type = "px", value = tonumber(pxValue) }
end
-- Handle minmax(min, max)
local minStr, maxStr = trackSize:match("^minmax%s*%(([^,]+),%s*([^)]+)%)$")
if minStr and maxStr then
local minTrack = Grid.parseTrackSize(minStr:match("^%s*(.-)%s*$"))
local maxTrack = Grid.parseTrackSize(maxStr:match("^%s*(.-)%s*$"))
return { type = "minmax", min = minTrack, max = maxTrack }
end
-- Default to auto for unrecognized formats
return { type = "auto" }
end
--- Parse track list (e.g., "1fr 2fr 100px" or "repeat(3, 1fr)")
---@param trackList string|table
---@return table -- Array of parsed track sizes
function Grid.parseTrackList(trackList)
if type(trackList) == "table" then
local result = {}
for _, track in ipairs(trackList) do
table.insert(result, Grid.parseTrackSize(track))
end
return result
end
if type(trackList) ~= "string" then
return {}
end
local tracks = {}
-- Handle repeat() function
local repeatMatch = trackList:match("^repeat%s*%(([^)]+)%)$")
if repeatMatch then
local countStr, pattern = repeatMatch:match("^%s*(%d+)%s*,%s*(.+)%s*$")
if countStr and pattern then
local count = tonumber(countStr)
local repeatTracks = Grid.parseTrackList(pattern)
for i = 1, count do
for _, track in ipairs(repeatTracks) do
table.insert(tracks, track)
end
end
return tracks
end
end
-- Split by whitespace and parse each track
for trackStr in trackList:gmatch("%S+") do
table.insert(tracks, Grid.parseTrackSize(trackStr))
end
return tracks
end
--- Resolve track sizes to actual pixel values
---@param tracks table -- Array of parsed track sizes
---@param availableSize number -- Available space in pixels
---@param gap number -- Gap between tracks
---@return table -- Array of resolved pixel sizes
function Grid.resolveTrackSizes(tracks, availableSize, gap)
if #tracks == 0 then
return {}
end
-- Calculate total gap space
local totalGapSize = (#tracks - 1) * gap
local remainingSpace = availableSize - totalGapSize
local resolvedSizes = {}
local frTracks = {}
local frTotal = 0
-- First pass: resolve fixed sizes and collect fr tracks
for i, track in ipairs(tracks) do
if track.type == "px" then
resolvedSizes[i] = track.value
remainingSpace = remainingSpace - track.value
elseif track.type == "%" then
local size = (track.value / 100) * availableSize
resolvedSizes[i] = size
remainingSpace = remainingSpace - size
elseif track.type == "fr" then
table.insert(frTracks, i)
frTotal = frTotal + track.value
elseif track.type == "auto" or track.type == "min-content" or track.type == "max-content" then
-- For now, treat auto/min-content/max-content as equal distribution of remaining space
resolvedSizes[i] = 0 -- Will be calculated in second pass
elseif track.type == "minmax" then
-- Simplified: use max if available, otherwise min
-- This is a basic implementation - full minmax is complex
if track.max.type == "fr" then
table.insert(frTracks, i)
frTotal = frTotal + track.max.value
else
local maxSize = Grid.parseTrackSize(track.max)
if maxSize.type == "px" then
resolvedSizes[i] = maxSize.value
remainingSpace = remainingSpace - maxSize.value
else
resolvedSizes[i] = 0
end
end
end
end
-- Second pass: distribute remaining space to fr tracks
if frTotal > 0 and remainingSpace > 0 then
local frUnit = remainingSpace / frTotal
for _, i in ipairs(frTracks) do
local track = tracks[i]
if track.type == "fr" then
resolvedSizes[i] = track.value * frUnit
elseif track.type == "minmax" and track.max.type == "fr" then
resolvedSizes[i] = track.max.value * frUnit
end
end
else
-- No space left for fr tracks
for _, i in ipairs(frTracks) do
resolvedSizes[i] = 0
end
end
-- Third pass: handle auto tracks (equal distribution of any remaining space)
local autoTracks = {}
for i, track in ipairs(tracks) do
if track.type == "auto" or track.type == "min-content" or track.type == "max-content" then
if resolvedSizes[i] == 0 then
table.insert(autoTracks, i)
end
end
end
if #autoTracks > 0 then
local autoSize = math.max(0, remainingSpace / #autoTracks)
for _, i in ipairs(autoTracks) do
resolvedSizes[i] = autoSize
end
end
return resolvedSizes
end
--- Parse grid line placement (e.g., "1", "2 / 4", "span 2")
---@param placement string|number|nil
---@return table -- { start: number?, end: number?, span: number? }
function Grid.parsePlacement(placement)
if not placement then
return { start = nil, end_ = nil, span = nil }
end
if type(placement) == "number" then
return { start = placement, end_ = nil, span = nil }
end
if type(placement) ~= "string" then
return { start = nil, end_ = nil, span = nil }
end
-- Handle "span N" format
local spanValue = placement:match("^span%s+(%d+)$")
if spanValue then
return { start = nil, end_ = nil, span = tonumber(spanValue) }
end
-- Handle "start / end" format
local startStr, endStr = placement:match("^(%d+)%s*/%s*(%d+)$")
if startStr and endStr then
return { start = tonumber(startStr), end_ = tonumber(endStr), span = nil }
end
-- Handle single number
local lineNum = tonumber(placement)
if lineNum then
return { start = lineNum, end_ = nil, span = nil }
end
return { start = nil, end_ = nil, span = nil }
end
--- Calculate grid item placement
---@param item Element
---@param columnCount number
---@param rowCount number
---@param autoPlacementCursor {column: number, row: number}
---@param gridAutoFlow string
---@return table -- { columnStart: number, columnEnd: number, rowStart: number, rowEnd: number }
function Grid.calculateItemPlacement(item, columnCount, rowCount, autoPlacementCursor, gridAutoFlow)
local columnPlacement = Grid.parsePlacement(item.gridColumn)
local rowPlacement = Grid.parsePlacement(item.gridRow)
local columnStart, columnEnd, rowStart, rowEnd
-- Determine column placement
if columnPlacement.start and columnPlacement.end_ then
columnStart = columnPlacement.start
columnEnd = columnPlacement.end_
elseif columnPlacement.start and columnPlacement.span then
columnStart = columnPlacement.start
columnEnd = columnStart + columnPlacement.span
elseif columnPlacement.start then
columnStart = columnPlacement.start
columnEnd = columnStart + 1
elseif columnPlacement.span then
-- Auto-place with span
columnStart = autoPlacementCursor.column
columnEnd = columnStart + columnPlacement.span
else
-- Auto-place
columnStart = autoPlacementCursor.column
columnEnd = columnStart + 1
end
-- Determine row placement
if rowPlacement.start and rowPlacement.end_ then
rowStart = rowPlacement.start
rowEnd = rowPlacement.end_
elseif rowPlacement.start and rowPlacement.span then
rowStart = rowPlacement.start
rowEnd = rowStart + rowPlacement.span
elseif rowPlacement.start then
rowStart = rowPlacement.start
rowEnd = rowStart + 1
elseif rowPlacement.span then
-- Auto-place with span
rowStart = autoPlacementCursor.row
rowEnd = rowStart + rowPlacement.span
else
-- Auto-place
rowStart = autoPlacementCursor.row
rowEnd = rowStart + 1
end
return {
columnStart = columnStart,
columnEnd = columnEnd,
rowStart = rowStart,
rowEnd = rowEnd,
}
end
--- Layout grid items within a grid container
---@param element Element -- Grid container element ---@param element Element -- Grid container element
function Grid.layoutGridItems(element) function Grid.layoutGridItems(element)
if not element.gridTemplateColumns and not element.gridTemplateRows then local rows = element.gridRows or 1
-- No grid template defined, fall back to single column/row local columns = element.gridColumns or 1
element.gridTemplateColumns = element.gridTemplateColumns or "1fr"
element.gridTemplateRows = element.gridTemplateRows or "auto"
end
-- Parse track definitions
local columnTracks = Grid.parseTrackList(element.gridTemplateColumns or "1fr")
local rowTracks = Grid.parseTrackList(element.gridTemplateRows or "auto")
-- Calculate available space -- Calculate available space
local availableWidth = element.width - element.padding.left - element.padding.right local availableWidth = element.width - element.padding.left - element.padding.right
local availableHeight = element.height - element.padding.top - element.padding.bottom local availableHeight = element.height - element.padding.top - element.padding.bottom
-- Resolve track sizes -- Get gaps
local columnGap = element.columnGap or 0 local columnGap = element.columnGap or 0
local rowGap = element.rowGap or 0 local rowGap = element.rowGap or 0
local columnSizes = Grid.resolveTrackSizes(columnTracks, availableWidth, columnGap) -- Calculate cell sizes (equal distribution)
local rowSizes = Grid.resolveTrackSizes(rowTracks, availableHeight, rowGap) local totalColumnGaps = (columns - 1) * columnGap
local totalRowGaps = (rows - 1) * rowGap
-- Calculate column and row positions local cellWidth = (availableWidth - totalColumnGaps) / columns
local columnPositions = {} local cellHeight = (availableHeight - totalRowGaps) / rows
local rowPositions = {}
-- Get children that participate in grid layout
local currentX = element.x + element.padding.left local gridChildren = {}
for i, size in ipairs(columnSizes) do
columnPositions[i] = currentX
currentX = currentX + size + columnGap
end
columnPositions[#columnSizes + 1] = currentX - columnGap -- End position
local currentY = element.y + element.padding.top
for i, size in ipairs(rowSizes) do
rowPositions[i] = currentY
currentY = currentY + size + rowGap
end
rowPositions[#rowSizes + 1] = currentY - rowGap -- End position
-- Auto-placement cursor
local autoPlacementCursor = { column = 1, row = 1 }
local gridAutoFlow = element.gridAutoFlow or GridAutoFlow.ROW
-- Place grid items
for _, child in ipairs(element.children) do for _, child in ipairs(element.children) do
-- Skip explicitly absolute positioned children
if not (child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute) then if not (child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute) then
local placement = Grid.calculateItemPlacement(child, #columnSizes, #rowSizes, autoPlacementCursor, gridAutoFlow) table.insert(gridChildren, child)
end
-- Ensure placement is within bounds, expand grid if necessary end
local columnStart = math.max(1, math.min(placement.columnStart, #columnSizes + 1))
local columnEnd = math.max(columnStart + 1, math.min(placement.columnEnd, #columnSizes + 1)) -- Place children in grid cells
local rowStart = math.max(1, math.min(placement.rowStart, #rowSizes + 1)) for i, child in ipairs(gridChildren) do
local rowEnd = math.max(rowStart + 1, math.min(placement.rowEnd, #rowSizes + 1)) -- Calculate row and column (0-indexed for calculation)
local index = i - 1
-- Calculate item position and size local col = index % columns
local itemX = columnPositions[columnStart] or element.x local row = math.floor(index / columns)
local itemY = rowPositions[rowStart] or element.y
local itemWidth = (columnPositions[columnEnd] or (element.x + element.width)) - itemX - columnGap -- Skip if we've exceeded the grid
local itemHeight = (rowPositions[rowEnd] or (element.y + element.height)) - itemY - rowGap if row >= rows then
break
-- Apply alignment within grid cell end
local effectiveJustifySelf = child.justifySelf or element.justifyItems or GridJustifyItems.STRETCH
local effectiveAlignSelf = child.alignSelf or element.alignItems or AlignItems.STRETCH -- Calculate cell position
local cellX = element.x + element.padding.left + (col * (cellWidth + columnGap))
-- Handle justifySelf (horizontal alignment) local cellY = element.y + element.padding.top + (row * (cellHeight + rowGap))
if effectiveJustifySelf == GridJustifyItems.STRETCH or effectiveJustifySelf == "stretch" then
child.x = itemX + child.padding.left -- Apply alignment within grid cell (default to stretch)
child.width = itemWidth - child.padding.left - child.padding.right local effectiveAlignItems = element.alignItems or AlignItems.STRETCH
elseif effectiveJustifySelf == GridJustifyItems.START or effectiveJustifySelf == "start" or effectiveJustifySelf == "flex-start" then
child.x = itemX + child.padding.left -- Stretch child to fill cell by default
-- Keep child's natural width if effectiveAlignItems == AlignItems.STRETCH or effectiveAlignItems == "stretch" then
elseif effectiveJustifySelf == GridJustifyItems.END or effectiveJustifySelf == "end" or effectiveJustifySelf == "flex-end" then child.x = cellX + child.padding.left
child.x = itemX + itemWidth - child.width - child.padding.right child.y = cellY + child.padding.top
elseif effectiveJustifySelf == GridJustifyItems.CENTER or effectiveJustifySelf == "center" then child.width = cellWidth - child.padding.left - child.padding.right
child.x = itemX + (itemWidth - child.width) / 2 child.height = cellHeight - child.padding.top - child.padding.bottom
else -- Disable auto-sizing when stretched by grid
-- Default to stretch child.autosizing.width = false
child.x = itemX + child.padding.left child.autosizing.height = false
child.width = itemWidth - child.padding.left - child.padding.right elseif effectiveAlignItems == AlignItems.CENTER or effectiveAlignItems == "center" then
end child.x = cellX + (cellWidth - child.width) / 2
child.y = cellY + (cellHeight - child.height) / 2
-- Handle alignSelf (vertical alignment) elseif effectiveAlignItems == AlignItems.FLEX_START or effectiveAlignItems == "flex-start" or effectiveAlignItems == "start" then
if effectiveAlignSelf == AlignItems.STRETCH or effectiveAlignSelf == "stretch" then child.x = cellX + child.padding.left
child.y = itemY + child.padding.top child.y = cellY + child.padding.top
child.height = itemHeight - child.padding.top - child.padding.bottom elseif effectiveAlignItems == AlignItems.FLEX_END or effectiveAlignItems == "flex-end" or effectiveAlignItems == "end" then
elseif child.x = cellX + cellWidth - child.width - child.padding.right
effectiveAlignSelf == AlignItems.FLEX_START child.y = cellY + cellHeight - child.height - child.padding.bottom
or effectiveAlignSelf == "flex-start" else
or effectiveAlignSelf == "start" -- Default to stretch
then child.x = cellX + child.padding.left
child.y = itemY + child.padding.top child.y = cellY + child.padding.top
-- Keep child's natural height child.width = cellWidth - child.padding.left - child.padding.right
elseif child.height = cellHeight - child.padding.top - child.padding.bottom
effectiveAlignSelf == AlignItems.FLEX_END -- Disable auto-sizing when stretched by grid
or effectiveAlignSelf == "flex-end" child.autosizing.width = false
or effectiveAlignSelf == "end" child.autosizing.height = false
then end
child.y = itemY + itemHeight - child.height - child.padding.bottom
elseif effectiveAlignSelf == AlignItems.CENTER or effectiveAlignSelf == "center" then -- Layout child's children if it has any
child.y = itemY + (itemHeight - child.height) / 2 if #child.children > 0 then
else child:layoutChildren()
-- Default to stretch
child.y = itemY + child.padding.top
child.height = itemHeight - child.padding.top - child.padding.bottom
end
-- Update auto-placement cursor
if gridAutoFlow == GridAutoFlow.ROW or gridAutoFlow == "row" then
autoPlacementCursor.column = columnEnd
if autoPlacementCursor.column > #columnSizes then
autoPlacementCursor.column = 1
autoPlacementCursor.row = autoPlacementCursor.row + 1
end
elseif gridAutoFlow == GridAutoFlow.COLUMN or gridAutoFlow == "column" then
autoPlacementCursor.row = rowEnd
if autoPlacementCursor.row > #rowSizes then
autoPlacementCursor.row = 1
autoPlacementCursor.column = autoPlacementCursor.column + 1
end
end
-- Layout child's children if it has any
if #child.children > 0 then
child:layoutChildren()
end
end end
end end
end end
@@ -995,18 +627,10 @@ end
---@field transition TransitionProps -- Transition settings for animations ---@field transition TransitionProps -- Transition settings for animations
---@field callback function? -- Callback function for click events ---@field callback function? -- Callback function for click events
---@field units table -- Original unit specifications for responsive behavior ---@field units table -- Original unit specifications for responsive behavior
---@field gridTemplateColumns string|table? -- Grid column track definitions ---@field gridRows number? -- Number of rows in the grid
---@field gridTemplateRows string|table? -- Grid row track definitions ---@field gridColumns number? -- Number of columns in the grid
---@field gridAutoFlow GridAutoFlow? -- Grid auto-placement algorithm
---@field gridAutoColumns string? -- Size of auto-generated columns
---@field gridAutoRows string? -- Size of auto-generated rows
---@field columnGap number|string? -- Gap between grid columns ---@field columnGap number|string? -- Gap between grid columns
---@field rowGap number|string? -- Gap between grid rows ---@field rowGap number|string? -- Gap between grid rows
---@field gridColumn string|number? -- Grid item column placement
---@field gridRow string|number? -- Grid item row placement
---@field gridArea string? -- Grid item named area placement
---@field justifyItems GridJustifyItems? -- Default horizontal alignment for grid items
---@field alignItems AlignItems? -- Default vertical alignment for grid items
local Element = {} local Element = {}
Element.__index = Element Element.__index = Element
@@ -1046,17 +670,10 @@ Element.__index = Element
---@field callback function? -- Callback function for click events ---@field callback function? -- Callback function for click events
---@field transform table? -- Transform properties for animations and styling ---@field transform table? -- Transform properties for animations and styling
---@field transition table? -- Transition settings for animations ---@field transition table? -- Transition settings for animations
---@field gridTemplateColumns string|table? -- Grid column track definitions (e.g., "1fr 2fr 100px" or {"1fr", "2fr", "100px"}) ---@field gridRows number? -- Number of rows in the grid (default: 1)
---@field gridTemplateRows string|table? -- Grid row track definitions ---@field gridColumns number? -- Number of columns in the grid (default: 1)
---@field gridAutoFlow GridAutoFlow? -- Grid auto-placement algorithm (default: ROW)
---@field gridAutoColumns string? -- Size of auto-generated columns (default: "auto")
---@field gridAutoRows string? -- Size of auto-generated rows (default: "auto")
---@field columnGap number|string? -- Gap between grid columns ---@field columnGap number|string? -- Gap between grid columns
---@field rowGap number|string? -- Gap between grid rows ---@field rowGap number|string? -- Gap between grid rows
---@field gridColumn string|number? -- Grid item column placement (e.g., "1", "2 / 4", "span 2")
---@field gridRow string|number? -- Grid item row placement
---@field gridArea string? -- Grid item named area placement
---@field justifyItems GridJustifyItems? -- Default horizontal alignment for grid items
local ElementProps = {} local ElementProps = {}
---@param props ElementProps ---@param props ElementProps
@@ -1298,10 +915,7 @@ function Element.new(props)
end end
end end
-- Grid item properties (set early so they're available when addChild is called) -- Grid properties are set later in the constructor
self.gridColumn = props.gridColumn
self.gridRow = props.gridRow
self.gridArea = props.gridArea
------ add hereditary ------ ------ add hereditary ------
if props.parent == nil then if props.parent == nil then
@@ -1362,12 +976,15 @@ function Element.new(props)
elseif props.positioning == Positioning.FLEX then elseif props.positioning == Positioning.FLEX then
self.positioning = Positioning.FLEX self.positioning = Positioning.FLEX
self._explicitlyAbsolute = false self._explicitlyAbsolute = false
elseif props.positioning == Positioning.GRID then
self.positioning = Positioning.GRID
self._explicitlyAbsolute = false
else else
-- Default: children in flex containers participate in flex layout -- Default: children in flex/grid containers participate in parent's layout
-- children in absolute containers default to absolute -- children in absolute containers default to absolute
if self.parent.positioning == Positioning.FLEX then if self.parent.positioning == Positioning.FLEX or self.parent.positioning == Positioning.GRID then
self.positioning = Positioning.ABSOLUTE -- They are positioned BY flex, not AS flex self.positioning = Positioning.ABSOLUTE -- They are positioned BY flex/grid, not AS flex/grid
self._explicitlyAbsolute = false -- Participate in parent's flex layout self._explicitlyAbsolute = false -- Participate in parent's layout
else else
self.positioning = Positioning.ABSOLUTE self.positioning = Positioning.ABSOLUTE
self._explicitlyAbsolute = false -- Default for absolute containers self._explicitlyAbsolute = false -- Default for absolute containers
@@ -1532,14 +1149,8 @@ function Element.new(props)
-- Grid container properties -- Grid container properties
if self.positioning == Positioning.GRID then if self.positioning == Positioning.GRID then
self.gridTemplateColumns = props.gridTemplateColumns self.gridRows = props.gridRows or 1
self.gridTemplateRows = props.gridTemplateRows self.gridColumns = props.gridColumns or 1
self.gridAutoFlow = props.gridAutoFlow or GridAutoFlow.ROW
self.gridAutoColumns = props.gridAutoColumns or "auto"
self.gridAutoRows = props.gridAutoRows or "auto"
self.justifyContent = props.justifyContent or GridJustifyContent.START
self.alignContent = props.alignContent or GridAlignContent.START
self.justifyItems = props.justifyItems or GridJustifyItems.STRETCH
self.alignItems = props.alignItems or AlignItems.STRETCH self.alignItems = props.alignItems or AlignItems.STRETCH
-- Handle columnGap and rowGap -- Handle columnGap and rowGap

View File

@@ -1,226 +0,0 @@
-- Example demonstrating basic CSS Grid layout
-- Shows how to create grid containers and position items
package.path = package.path .. ";?.lua"
require("testing/loveStub")
local FlexLove = require("FlexLove")
local Gui = FlexLove.GUI
local Color = FlexLove.Color
local enums = FlexLove.enums
print("=== Basic Grid Layout Examples ===\n")
-- Example 1: Simple 3-column grid
print("1. Simple 3-Column Grid")
print(" Grid with equal columns using fr units")
local grid1 = Gui.new({
x = 50,
y = 50,
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr 1fr",
gridTemplateRows = "auto auto",
columnGap = 10,
rowGap = 10,
background = Color.new(0.9, 0.9, 0.9, 1),
padding = { horizontal = 20, vertical = 20 },
})
-- Add grid items
for i = 1, 6 do
Gui.new({
parent = grid1,
width = 50,
height = 50,
background = Color.new(0.2, 0.5, 0.8, 1),
text = "Item " .. i,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
print(" Grid container: 600x400, 3 columns (1fr each), 2 rows (auto)")
print(" Column gap: 10px, Row gap: 10px")
print(" Items: 6 items auto-placed in grid\n")
-- Example 2: Mixed column sizes
print("2. Mixed Column Sizes")
print(" Grid with different column widths")
Gui.destroy()
local grid2 = Gui.new({
x = 50,
y = 50,
width = 800,
height = 300,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "200px 1fr 2fr",
gridTemplateRows = "100px 100px",
columnGap = 15,
rowGap = 15,
background = Color.new(0.9, 0.9, 0.9, 1),
padding = { horizontal = 20, vertical = 20 },
})
local labels = { "Sidebar", "Content", "Main", "Footer", "Info", "Extra" }
for i = 1, 6 do
Gui.new({
parent = grid2,
background = Color.new(0.3, 0.6, 0.3, 1),
text = labels[i],
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
print(" Columns: 200px (fixed), 1fr, 2fr (flexible)")
print(" The flexible columns share remaining space proportionally\n")
-- Example 3: Explicit item placement
print("3. Explicit Grid Item Placement")
print(" Items placed at specific grid positions")
Gui.destroy()
local grid3 = Gui.new({
x = 50,
y = 50,
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr 1fr",
gridTemplateRows = "1fr 1fr 1fr",
columnGap = 10,
rowGap = 10,
background = Color.new(0.9, 0.9, 0.9, 1),
padding = { horizontal = 20, vertical = 20 },
})
-- Header spanning all columns
Gui.new({
parent = grid3,
gridColumn = "1 / 4", -- Span from column 1 to 4 (all 3 columns)
gridRow = 1,
background = Color.new(0.8, 0.3, 0.3, 1),
text = "Header (spans all columns)",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Sidebar spanning 2 rows
Gui.new({
parent = grid3,
gridColumn = 1,
gridRow = "2 / 4", -- Span from row 2 to 4 (2 rows)
background = Color.new(0.3, 0.3, 0.8, 1),
text = "Sidebar",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Main content area
Gui.new({
parent = grid3,
gridColumn = "2 / 4", -- Span columns 2-3
gridRow = 2,
background = Color.new(0.3, 0.8, 0.3, 1),
text = "Main Content",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Footer spanning columns 2-3
Gui.new({
parent = grid3,
gridColumn = "2 / 4",
gridRow = 3,
background = Color.new(0.8, 0.8, 0.3, 1),
text = "Footer",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
print(" Header: spans columns 1-3, row 1")
print(" Sidebar: column 1, spans rows 2-3")
print(" Main: spans columns 2-3, row 2")
print(" Footer: spans columns 2-3, row 3\n")
-- Example 4: Using repeat() function
print("4. Using repeat() Function")
print(" Create multiple columns with repeat notation")
Gui.destroy()
local grid4 = Gui.new({
x = 50,
y = 50,
width = 800,
height = 300,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "repeat(4, 1fr)", -- Creates 4 equal columns
gridTemplateRows = "repeat(2, 1fr)", -- Creates 2 equal rows
columnGap = 10,
rowGap = 10,
background = Color.new(0.9, 0.9, 0.9, 1),
padding = { horizontal = 20, vertical = 20 },
})
for i = 1, 8 do
Gui.new({
parent = grid4,
background = Color.new(0.5, 0.3, 0.7, 1),
text = "Box " .. i,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
print(" gridTemplateColumns: repeat(4, 1fr)")
print(" gridTemplateRows: repeat(2, 1fr)")
print(" Creates a 4x2 grid with 8 equal cells\n")
-- Example 5: Percentage-based grid
print("5. Percentage-Based Grid")
print(" Using percentage units for columns")
Gui.destroy()
local grid5 = Gui.new({
x = 50,
y = 50,
width = 600,
height = 200,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "25% 50% 25%",
gridTemplateRows = "100%",
columnGap = 0,
rowGap = 0,
background = Color.new(0.9, 0.9, 0.9, 1),
})
local colors = {
Color.new(0.8, 0.2, 0.2, 1),
Color.new(0.2, 0.8, 0.2, 1),
Color.new(0.2, 0.2, 0.8, 1),
}
for i = 1, 3 do
Gui.new({
parent = grid5,
background = colors[i],
text = (i == 1 and "25%" or i == 2 and "50%" or "25%"),
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
print(" Columns: 25%, 50%, 25%")
print(" Perfect for layouts with specific proportions\n")
print("=== Summary ===")
print("• Set positioning = Positioning.GRID to create a grid container")
print("• Use gridTemplateColumns and gridTemplateRows to define track sizes")
print("• Supported units: px, %, fr, auto, repeat()")
print("• Use columnGap and rowGap for spacing between tracks")
print("• Use gridColumn and gridRow on children for explicit placement")
print("• Use 'start / end' syntax to span multiple tracks")
print("• Items auto-place if no explicit position is set")

View File

@@ -1,249 +0,0 @@
-- Example demonstrating responsive grid layouts with viewport units
-- Shows how grids adapt to different screen sizes
package.path = package.path .. ";?.lua"
require("testing/loveStub")
local FlexLove = require("FlexLove")
local Gui = FlexLove.GUI
local Color = FlexLove.Color
local enums = FlexLove.enums
print("=== Responsive Grid Layout Examples ===\n")
-- Example 1: Dashboard layout with responsive grid
print("1. Dashboard Layout")
print(" Responsive grid using viewport units")
local dashboard = Gui.new({
x = 0,
y = 0,
width = "100vw",
height = "100vh",
positioning = enums.Positioning.GRID,
gridTemplateColumns = "200px 1fr 1fr",
gridTemplateRows = "60px 1fr 1fr 80px",
columnGap = 10,
rowGap = 10,
background = Color.new(0.95, 0.95, 0.95, 1),
padding = { horizontal = 10, vertical = 10 },
})
-- Header (spans all columns)
Gui.new({
parent = dashboard,
gridColumn = "1 / 4",
gridRow = 1,
background = Color.new(0.2, 0.3, 0.5, 1),
text = "Dashboard Header",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
textSize = 24,
})
-- Sidebar (spans rows 2-3)
Gui.new({
parent = dashboard,
gridColumn = 1,
gridRow = "2 / 4",
background = Color.new(0.3, 0.3, 0.4, 1),
text = "Navigation",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Main content area (top)
Gui.new({
parent = dashboard,
gridColumn = "2 / 4",
gridRow = 2,
background = Color.new(1, 1, 1, 1),
text = "Main Content",
textColor = Color.new(0.2, 0.2, 0.2, 1),
textAlign = enums.TextAlign.CENTER,
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.8, 0.8, 0.8, 1),
})
-- Stats section (bottom left)
Gui.new({
parent = dashboard,
gridColumn = 2,
gridRow = 3,
background = Color.new(0.9, 0.95, 1, 1),
text = "Statistics",
textColor = Color.new(0.2, 0.2, 0.2, 1),
textAlign = enums.TextAlign.CENTER,
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.8, 0.8, 0.8, 1),
})
-- Activity feed (bottom right)
Gui.new({
parent = dashboard,
gridColumn = 3,
gridRow = 3,
background = Color.new(1, 0.95, 0.9, 1),
text = "Activity Feed",
textColor = Color.new(0.2, 0.2, 0.2, 1),
textAlign = enums.TextAlign.CENTER,
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.8, 0.8, 0.8, 1),
})
-- Footer (spans all columns)
Gui.new({
parent = dashboard,
gridColumn = "1 / 4",
gridRow = 4,
background = Color.new(0.2, 0.3, 0.5, 1),
text = "Footer - Copyright 2025",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
print(" Layout structure:")
print(" - Header: Full width, 60px height")
print(" - Sidebar: 200px wide, spans content rows")
print(" - Main content: Flexible width, top content area")
print(" - Stats & Activity: Split remaining space")
print(" - Footer: Full width, 80px height\n")
-- Example 2: Card grid with auto-flow
print("2. Card Grid with Auto-Flow")
print(" Grid that automatically places items")
Gui.destroy()
local cardGrid = Gui.new({
x = "5vw",
y = "5vh",
width = "90vw",
height = "90vh",
positioning = enums.Positioning.GRID,
gridTemplateColumns = "repeat(3, 1fr)",
gridTemplateRows = "auto",
gridAutoRows = "200px",
gridAutoFlow = enums.GridAutoFlow.ROW,
columnGap = 20,
rowGap = 20,
background = Color.new(0.9, 0.9, 0.9, 1),
padding = { horizontal = 20, vertical = 20 },
})
local cardColors = {
Color.new(0.8, 0.3, 0.3, 1),
Color.new(0.3, 0.8, 0.3, 1),
Color.new(0.3, 0.3, 0.8, 1),
Color.new(0.8, 0.8, 0.3, 1),
Color.new(0.8, 0.3, 0.8, 1),
Color.new(0.3, 0.8, 0.8, 1),
}
for i = 1, 9 do
local colorIndex = ((i - 1) % #cardColors) + 1
Gui.new({
parent = cardGrid,
background = cardColors[colorIndex],
text = "Card " .. i,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
textSize = 20,
})
end
print(" 9 cards in a 3-column grid")
print(" Auto-flow: ROW (fills rows first)")
print(" Auto-generated rows: 200px each\n")
-- Example 3: Nested grids
print("3. Nested Grid Layout")
print(" Grid containers within grid items")
Gui.destroy()
local outerGrid = Gui.new({
x = 50,
y = 50,
width = 700,
height = 500,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 2fr",
gridTemplateRows = "1fr 1fr",
columnGap = 15,
rowGap = 15,
background = Color.new(0.85, 0.85, 0.85, 1),
padding = { horizontal = 15, vertical = 15 },
})
-- Top-left: Simple item
Gui.new({
parent = outerGrid,
background = Color.new(0.5, 0.3, 0.7, 1),
text = "Simple Item",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Top-right: Nested grid
local nestedGrid1 = Gui.new({
parent = outerGrid,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr",
gridTemplateRows = "1fr 1fr",
columnGap = 5,
rowGap = 5,
background = Color.new(0.7, 0.7, 0.7, 1),
padding = { horizontal = 5, vertical = 5 },
})
for i = 1, 4 do
Gui.new({
parent = nestedGrid1,
background = Color.new(0.3, 0.6, 0.9, 1),
text = "A" .. i,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- Bottom-left: Another nested grid
local nestedGrid2 = Gui.new({
parent = outerGrid,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "repeat(3, 1fr)",
gridTemplateRows = "1fr",
columnGap = 5,
rowGap = 5,
background = Color.new(0.7, 0.7, 0.7, 1),
padding = { horizontal = 5, vertical = 5 },
})
for i = 1, 3 do
Gui.new({
parent = nestedGrid2,
background = Color.new(0.9, 0.6, 0.3, 1),
text = "B" .. i,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- Bottom-right: Simple item
Gui.new({
parent = outerGrid,
background = Color.new(0.3, 0.7, 0.5, 1),
text = "Another Item",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
print(" Outer grid: 2x2 layout")
print(" Top-right cell: 2x2 nested grid")
print(" Bottom-left cell: 1x3 nested grid")
print(" Other cells: Simple items\n")
print("=== Summary ===")
print("• Grids work with viewport units (vw, vh) for responsive layouts")
print("• Use gridAutoFlow to control automatic item placement")
print("• gridAutoRows/gridAutoColumns define sizes for auto-generated tracks")
print("• Grids can be nested within grid items")
print("• Combine fixed (px) and flexible (fr) units for hybrid layouts")
print("• Use gaps to create visual separation between grid items")

236
examples/SimpleGrid.lua Normal file
View File

@@ -0,0 +1,236 @@
-- Example demonstrating the simplified grid layout system
-- Shows how to create grids with simple row/column counts
package.path = package.path .. ";?.lua"
require("testing/loveStub")
local FlexLove = require("FlexLove")
local Gui = FlexLove.GUI
local Color = FlexLove.Color
local enums = FlexLove.enums
print("=== Simplified Grid Layout Examples ===\n")
-- Example 1: Simple 3x2 grid
print("1. Simple 3x2 Grid")
print(" Grid with 3 columns and 2 rows")
local grid1 = Gui.new({
x = 50,
y = 50,
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 3,
columnGap = 10,
rowGap = 10,
background = Color.new(0.9, 0.9, 0.9, 1),
padding = { horizontal = 20, vertical = 20 },
})
-- Add grid items - they auto-tile in order
for i = 1, 6 do
Gui.new({
parent = grid1,
background = Color.new(0.2, 0.5, 0.8, 1),
text = "Item " .. i,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
print(" Grid container: 600x400, 3 columns, 2 rows")
print(" Column gap: 10px, Row gap: 10px")
print(" Items: 6 items auto-tiled in order\n")
-- Example 2: Square grid (4x4)
print("2. Square Grid (4x4)")
print(" Perfect for icon grids or game boards")
Gui.destroy()
local grid2 = Gui.new({
x = 50,
y = 50,
width = 400,
height = 400,
positioning = enums.Positioning.GRID,
gridRows = 4,
gridColumns = 4,
columnGap = 5,
rowGap = 5,
background = Color.new(0.9, 0.9, 0.9, 1),
padding = { horizontal = 10, vertical = 10 },
})
for i = 1, 16 do
Gui.new({
parent = grid2,
background = Color.new(0.3, 0.6, 0.3, 1),
text = tostring(i),
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
print(" 16 items in a 4x4 grid")
print(" Each cell is equal size\n")
-- Example 3: Horizontal strip (1 row, multiple columns)
print("3. Horizontal Strip")
print(" Single row with multiple columns")
Gui.destroy()
local grid3 = Gui.new({
x = 50,
y = 50,
width = 800,
height = 100,
positioning = enums.Positioning.GRID,
gridRows = 1,
gridColumns = 5,
columnGap = 10,
rowGap = 0,
background = Color.new(0.9, 0.9, 0.9, 1),
padding = { horizontal = 10, vertical = 10 },
})
local labels = { "Home", "Products", "About", "Contact", "Login" }
for i = 1, 5 do
Gui.new({
parent = grid3,
background = Color.new(0.3, 0.3, 0.8, 1),
text = labels[i],
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
print(" Perfect for navigation bars\n")
-- Example 4: Vertical strip (multiple rows, 1 column)
print("4. Vertical Strip")
print(" Single column with multiple rows")
Gui.destroy()
local grid4 = Gui.new({
x = 50,
y = 50,
width = 200,
height = 500,
positioning = enums.Positioning.GRID,
gridRows = 5,
gridColumns = 1,
columnGap = 0,
rowGap = 10,
background = Color.new(0.9, 0.9, 0.9, 1),
padding = { horizontal = 10, vertical = 10 },
})
for i = 1, 5 do
Gui.new({
parent = grid4,
background = Color.new(0.5, 0.3, 0.7, 1),
text = "Option " .. i,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
print(" Perfect for sidebar menus\n")
-- Example 5: Nested grids
print("5. Nested Grids")
print(" Grid containers within grid cells")
Gui.destroy()
local outerGrid = Gui.new({
x = 50,
y = 50,
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
columnGap = 10,
rowGap = 10,
background = Color.new(0.85, 0.85, 0.85, 1),
padding = { horizontal = 10, vertical = 10 },
})
-- Top-left: Simple item
Gui.new({
parent = outerGrid,
background = Color.new(0.5, 0.3, 0.7, 1),
text = "Single Item",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Top-right: Nested 2x2 grid
local nestedGrid1 = Gui.new({
parent = outerGrid,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
columnGap = 5,
rowGap = 5,
background = Color.new(0.7, 0.7, 0.7, 1),
padding = { horizontal = 5, vertical = 5 },
})
for i = 1, 4 do
Gui.new({
parent = nestedGrid1,
background = Color.new(0.3, 0.6, 0.9, 1),
text = "A" .. i,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- Bottom-left: Nested 1x3 grid
local nestedGrid2 = Gui.new({
parent = outerGrid,
positioning = enums.Positioning.GRID,
gridRows = 1,
gridColumns = 3,
columnGap = 5,
rowGap = 5,
background = Color.new(0.7, 0.7, 0.7, 1),
padding = { horizontal = 5, vertical = 5 },
})
for i = 1, 3 do
Gui.new({
parent = nestedGrid2,
background = Color.new(0.9, 0.6, 0.3, 1),
text = "B" .. i,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- Bottom-right: Another simple item
Gui.new({
parent = outerGrid,
background = Color.new(0.3, 0.7, 0.5, 1),
text = "Another Item",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
print(" Outer grid: 2x2 layout")
print(" Top-right: 2x2 nested grid")
print(" Bottom-left: 1x3 nested grid\n")
print("=== Summary ===")
print("The simplified grid system provides:")
print("• Simple API: Just set gridRows and gridColumns")
print("• Auto-tiling: Children are placed in order automatically")
print("• Equal sizing: All cells are equal size")
print("• Gaps: Use columnGap and rowGap for spacing")
print("• Stretch: Children stretch to fill cells by default")
print("• Nesting: Grids can contain other grids")
print("\nNo need for complex track definitions, explicit placement, or spans!")
Gui.destroy()

View File

@@ -1,5 +1,5 @@
-- Grid Layout Tests -- Grid Layout Tests
-- Tests for CSS Grid layout functionality -- Tests for simplified grid layout functionality
package.path = package.path .. ";?.lua" package.path = package.path .. ";?.lua"
@@ -22,37 +22,6 @@ function TestGridLayout:tearDown()
Gui.destroy() Gui.destroy()
end end
-- ====================
-- Track Parsing Tests (via grid behavior)
-- ====================
function TestGridLayout:test_grid_accepts_various_track_formats()
-- Test that grid accepts various track size formats without errors
local grid1 = Gui.new({
x = 0,
y = 0,
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 2fr 50%",
gridTemplateRows = "auto 1fr",
})
lu.assertNotNil(grid1)
local grid2 = Gui.new({
x = 0,
y = 0,
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "repeat(3, 1fr)",
gridTemplateRows = "repeat(2, 100px)",
})
lu.assertNotNil(grid2)
Gui.destroy()
end
-- ==================== -- ====================
-- Basic Grid Layout Tests -- Basic Grid Layout Tests
-- ==================== -- ====================
@@ -64,13 +33,13 @@ function TestGridLayout:test_simple_grid_creation()
width = 600, width = 600,
height = 400, height = 400,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr 1fr", gridRows = 2,
gridTemplateRows = "1fr 1fr", gridColumns = 3,
}) })
lu.assertEquals(grid.positioning, enums.Positioning.GRID) lu.assertEquals(grid.positioning, enums.Positioning.GRID)
lu.assertEquals(grid.gridTemplateColumns, "1fr 1fr 1fr") lu.assertEquals(grid.gridRows, 2)
lu.assertEquals(grid.gridTemplateRows, "1fr 1fr") lu.assertEquals(grid.gridColumns, 3)
end end
function TestGridLayout:test_grid_with_gaps() function TestGridLayout:test_grid_with_gaps()
@@ -80,8 +49,8 @@ function TestGridLayout:test_grid_with_gaps()
width = 600, width = 600,
height = 400, height = 400,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr", gridRows = 2,
gridTemplateRows = "1fr 1fr", gridColumns = 2,
columnGap = 10, columnGap = 10,
rowGap = 20, rowGap = 20,
}) })
@@ -97,8 +66,8 @@ function TestGridLayout:test_grid_auto_placement()
width = 300, width = 300,
height = 200, height = 200,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 100px 100px", gridRows = 2,
gridTemplateRows = "100px 100px", gridColumns = 3,
columnGap = 0, columnGap = 0,
rowGap = 0, rowGap = 0,
padding = { horizontal = 0, vertical = 0 }, padding = { horizontal = 0, vertical = 0 },
@@ -127,75 +96,15 @@ function TestGridLayout:test_grid_auto_placement()
lu.assertAlmostEquals(items[4].y, 100, 1) lu.assertAlmostEquals(items[4].y, 100, 1)
end end
function TestGridLayout:test_grid_explicit_placement() function TestGridLayout:test_grid_equal_distribution()
local grid = Gui.new({ local grid = Gui.new({
x = 0, x = 0,
y = 0, y = 0,
width = 300, width = 300,
height = 200, height = 200,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 100px 100px", gridRows = 2,
gridTemplateRows = "100px 100px", gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
-- Place item at column 2, row 2
local item = Gui.new({
parent = grid,
gridColumn = 2,
gridRow = 2,
width = 50,
height = 50,
})
-- Should be at position (100, 100)
lu.assertAlmostEquals(item.x, 100, 1)
lu.assertAlmostEquals(item.y, 100, 1)
end
function TestGridLayout:test_grid_spanning()
local grid = Gui.new({
x = 0,
y = 0,
width = 300,
height = 200,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 100px 100px",
gridTemplateRows = "100px 100px",
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
-- Item spanning columns 1-3
local item = Gui.new({
parent = grid,
gridColumn = "1 / 4",
gridRow = 1,
width = 50,
height = 50,
})
-- Should start at x=0 and span 300px (3 columns)
lu.assertAlmostEquals(item.x, 0, 1)
lu.assertAlmostEquals(item.width, 300, 1)
end
-- ====================
-- Track Sizing Tests
-- ====================
function TestGridLayout:test_fr_unit_distribution()
local grid = Gui.new({
x = 0,
y = 0,
width = 300,
height = 200,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 2fr",
gridTemplateRows = "1fr",
columnGap = 0, columnGap = 0,
rowGap = 0, rowGap = 0,
padding = { horizontal = 0, vertical = 0 }, padding = { horizontal = 0, vertical = 0 },
@@ -203,105 +112,52 @@ function TestGridLayout:test_fr_unit_distribution()
local item1 = Gui.new({ local item1 = Gui.new({
parent = grid, parent = grid,
gridColumn = 1,
gridRow = 1,
width = 50, width = 50,
height = 50, height = 50,
}) })
local item2 = Gui.new({ local item2 = Gui.new({
parent = grid, parent = grid,
gridColumn = 2,
gridRow = 1,
width = 50, width = 50,
height = 50, height = 50,
}) })
-- First column should be 100px (1fr), second should be 200px (2fr) -- Each cell should be 150x100 (300/2 x 200/2)
lu.assertAlmostEquals(item1.x, 0, 1) lu.assertAlmostEquals(item1.width, 150, 1)
lu.assertAlmostEquals(item2.x, 100, 1) lu.assertAlmostEquals(item1.height, 100, 1)
lu.assertAlmostEquals(item1.width, 100, 1) lu.assertAlmostEquals(item2.x, 150, 1)
lu.assertAlmostEquals(item2.width, 200, 1) lu.assertAlmostEquals(item2.width, 150, 1)
end end
function TestGridLayout:test_mixed_units() function TestGridLayout:test_grid_stretch_behavior()
local grid = Gui.new({ local grid = Gui.new({
x = 0, x = 0,
y = 0, y = 0,
width = 400, width = 400,
height = 200, height = 200,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 1fr 2fr", gridRows = 1,
gridTemplateRows = "1fr", gridColumns = 3,
columnGap = 0, columnGap = 0,
rowGap = 0, rowGap = 0,
padding = { horizontal = 0, vertical = 0 }, padding = { horizontal = 0, vertical = 0 },
}) })
local item1 = Gui.new({ parent = grid, gridColumn = 1, gridRow = 1, width = 50, height = 50 }) local item1 = Gui.new({ parent = grid, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, gridColumn = 2, gridRow = 1, width = 50, height = 50 }) local item2 = Gui.new({ parent = grid, width = 50, height = 50 })
local item3 = Gui.new({ parent = grid, gridColumn = 3, gridRow = 1, width = 50, height = 50 }) local item3 = Gui.new({ parent = grid, width = 50, height = 50 })
-- First column: 100px (fixed) -- Each cell should be ~133.33px wide (400/3)
-- Remaining 300px divided as 1fr (100px) and 2fr (200px) -- Items should stretch to fill cells
lu.assertAlmostEquals(item1.width, 100, 1) lu.assertAlmostEquals(item1.width, 133.33, 1)
lu.assertAlmostEquals(item2.width, 100, 1) lu.assertAlmostEquals(item2.width, 133.33, 1)
lu.assertAlmostEquals(item3.width, 200, 1) lu.assertAlmostEquals(item3.width, 133.33, 1)
end
function TestGridLayout:test_percentage_columns()
local grid = Gui.new({
x = 0,
y = 0,
width = 400,
height = 200,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "25% 50% 25%",
gridTemplateRows = "1fr",
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local item1 = Gui.new({ parent = grid, gridColumn = 1, gridRow = 1, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, gridColumn = 2, gridRow = 1, width = 50, height = 50 })
local item3 = Gui.new({ parent = grid, gridColumn = 3, gridRow = 1, width = 50, height = 50 })
lu.assertAlmostEquals(item1.width, 100, 1) -- 25% of 400
lu.assertAlmostEquals(item2.width, 200, 1) -- 50% of 400
lu.assertAlmostEquals(item3.width, 100, 1) -- 25% of 400
end end
-- ==================== -- ====================
-- Alignment Tests -- Alignment Tests
-- ==================== -- ====================
function TestGridLayout:test_justify_items_stretch()
local grid = Gui.new({
x = 0,
y = 0,
width = 300,
height = 200,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 100px 100px",
gridTemplateRows = "100px",
justifyItems = enums.GridJustifyItems.STRETCH,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local item = Gui.new({
parent = grid,
gridColumn = 1,
gridRow = 1,
height = 50,
})
-- Item should stretch to fill cell width
lu.assertAlmostEquals(item.width, 100, 1)
end
function TestGridLayout:test_align_items_stretch() function TestGridLayout:test_align_items_stretch()
local grid = Gui.new({ local grid = Gui.new({
x = 0, x = 0,
@@ -309,8 +165,8 @@ function TestGridLayout:test_align_items_stretch()
width = 300, width = 300,
height = 200, height = 200,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px", gridRows = 2,
gridTemplateRows = "100px 100px", gridColumns = 1,
alignItems = enums.AlignItems.STRETCH, alignItems = enums.AlignItems.STRETCH,
columnGap = 0, columnGap = 0,
rowGap = 0, rowGap = 0,
@@ -319,12 +175,10 @@ function TestGridLayout:test_align_items_stretch()
local item = Gui.new({ local item = Gui.new({
parent = grid, parent = grid,
gridColumn = 1,
gridRow = 1,
width = 50, width = 50,
}) })
-- Item should stretch to fill cell height -- Item should stretch to fill cell height (200/2 = 100)
lu.assertAlmostEquals(item.height, 100, 1) lu.assertAlmostEquals(item.height, 100, 1)
end end
@@ -339,17 +193,18 @@ function TestGridLayout:test_column_gap()
width = 320, width = 320,
height = 100, height = 100,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 100px 100px", gridRows = 1,
gridTemplateRows = "100px", gridColumns = 3,
columnGap = 10, columnGap = 10,
rowGap = 0, rowGap = 0,
padding = { horizontal = 0, vertical = 0 }, padding = { horizontal = 0, vertical = 0 },
}) })
local item1 = Gui.new({ parent = grid, gridColumn = 1, gridRow = 1, width = 50, height = 50 }) local item1 = Gui.new({ parent = grid, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, gridColumn = 2, gridRow = 1, width = 50, height = 50 }) local item2 = Gui.new({ parent = grid, width = 50, height = 50 })
local item3 = Gui.new({ parent = grid, gridColumn = 3, gridRow = 1, width = 50, height = 50 }) local item3 = Gui.new({ parent = grid, width = 50, height = 50 })
-- Total width: 320, gaps: 2*10=20, available: 300, per cell: 100
lu.assertAlmostEquals(item1.x, 0, 1) lu.assertAlmostEquals(item1.x, 0, 1)
lu.assertAlmostEquals(item2.x, 110, 1) -- 100 + 10 gap lu.assertAlmostEquals(item2.x, 110, 1) -- 100 + 10 gap
lu.assertAlmostEquals(item3.x, 220, 1) -- 100 + 10 + 100 + 10 lu.assertAlmostEquals(item3.x, 220, 1) -- 100 + 10 + 100 + 10
@@ -362,17 +217,18 @@ function TestGridLayout:test_row_gap()
width = 100, width = 100,
height = 320, height = 320,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px", gridRows = 3,
gridTemplateRows = "100px 100px 100px", gridColumns = 1,
columnGap = 0, columnGap = 0,
rowGap = 10, rowGap = 10,
padding = { horizontal = 0, vertical = 0 }, padding = { horizontal = 0, vertical = 0 },
}) })
local item1 = Gui.new({ parent = grid, gridColumn = 1, gridRow = 1, width = 50, height = 50 }) local item1 = Gui.new({ parent = grid, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, gridColumn = 1, gridRow = 2, width = 50, height = 50 }) local item2 = Gui.new({ parent = grid, width = 50, height = 50 })
local item3 = Gui.new({ parent = grid, gridColumn = 1, gridRow = 3, width = 50, height = 50 }) local item3 = Gui.new({ parent = grid, width = 50, height = 50 })
-- Total height: 320, gaps: 2*10=20, available: 300, per cell: 100
lu.assertAlmostEquals(item1.y, 0, 1) lu.assertAlmostEquals(item1.y, 0, 1)
lu.assertAlmostEquals(item2.y, 110, 1) -- 100 + 10 gap lu.assertAlmostEquals(item2.y, 110, 1) -- 100 + 10 gap
lu.assertAlmostEquals(item3.y, 220, 1) -- 100 + 10 + 100 + 10 lu.assertAlmostEquals(item3.y, 220, 1) -- 100 + 10 + 100 + 10
@@ -389,8 +245,8 @@ function TestGridLayout:test_nested_grids()
width = 400, width = 400,
height = 400, height = 400,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr", gridRows = 2,
gridTemplateRows = "1fr 1fr", gridColumns = 2,
columnGap = 0, columnGap = 0,
rowGap = 0, rowGap = 0,
padding = { horizontal = 0, vertical = 0 }, padding = { horizontal = 0, vertical = 0 },
@@ -398,29 +254,92 @@ function TestGridLayout:test_nested_grids()
local innerGrid = Gui.new({ local innerGrid = Gui.new({
parent = outerGrid, parent = outerGrid,
gridColumn = 1,
gridRow = 1,
positioning = enums.Positioning.GRID, positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr", gridRows = 2,
gridTemplateRows = "1fr 1fr", gridColumns = 2,
columnGap = 0, columnGap = 0,
rowGap = 0, rowGap = 0,
padding = { horizontal = 0, vertical = 0 }, padding = { horizontal = 0, vertical = 0 },
}) })
local innerItem = Gui.new({ -- Add items to inner grid
parent = innerGrid, local item1 = Gui.new({ parent = innerGrid, width = 50, height = 50 })
gridColumn = 2, local item2 = Gui.new({ parent = innerGrid, width = 50, height = 50 })
gridRow = 2,
-- Inner grid should be stretched to fill outer grid cell (200x200)
lu.assertAlmostEquals(innerGrid.width, 200, 1)
lu.assertAlmostEquals(innerGrid.height, 200, 1)
-- Items in inner grid should be positioned correctly
-- Each cell in inner grid is 100x100
lu.assertAlmostEquals(item1.x, 0, 1)
lu.assertAlmostEquals(item1.y, 0, 1)
lu.assertAlmostEquals(item2.x, 100, 1)
lu.assertAlmostEquals(item2.y, 0, 1)
end
-- ====================
-- Edge Cases
-- ====================
function TestGridLayout:test_more_items_than_cells()
local grid = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local items = {}
for i = 1, 6 do
items[i] = Gui.new({
parent = grid,
width = 50,
height = 50,
})
end
-- First 4 items should be positioned
lu.assertAlmostEquals(items[1].x, 0, 1)
lu.assertAlmostEquals(items[4].x, 100, 1)
lu.assertAlmostEquals(items[4].y, 100, 1)
-- Items 5 and 6 should not be laid out (remain at parent position)
-- This is acceptable behavior - they're just not visible in the grid
end
function TestGridLayout:test_single_cell_grid()
local grid = Gui.new({
x = 0,
y = 0,
width = 100,
height = 100,
positioning = enums.Positioning.GRID,
gridRows = 1,
gridColumns = 1,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local item = Gui.new({
parent = grid,
width = 50, width = 50,
height = 50, height = 50,
}) })
-- Inner grid should be in top-left quadrant (200x200) -- Item should stretch to fill the entire grid
-- Inner item should be in bottom-right of that (at 100, 100 relative to inner grid) lu.assertAlmostEquals(item.x, 0, 1)
lu.assertAlmostEquals(innerItem.x, 100, 1) lu.assertAlmostEquals(item.y, 0, 1)
lu.assertAlmostEquals(innerItem.y, 100, 1) lu.assertAlmostEquals(item.width, 100, 1)
lu.assertAlmostEquals(item.height, 100, 1)
end end
print("Running Grid Layout Tests...") print("Running Simplified Grid Layout Tests...")
os.exit(lu.LuaUnit.run()) os.exit(lu.LuaUnit.run())