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
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.FlexDirection,
enums.JustifyContent,
@@ -142,11 +113,7 @@ local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, Text
enums.TextAlign,
enums.AlignSelf,
enums.JustifySelf,
enums.FlexWrap,
enums.GridAutoFlow,
enums.GridJustifyItems,
enums.GridAlignContent,
enums.GridJustifyContent
enums.FlexWrap
-- ====================
-- Units System
@@ -301,417 +268,83 @@ end
-- Grid System
-- ====================
--- Grid track parsing and layout calculations
--- Simple grid layout calculations
local Grid = {}
--- Parse a single track size value
---@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
--- Layout grid items within a grid container using simple row/column counts
---@param element Element -- Grid container element
function Grid.layoutGridItems(element)
if not element.gridTemplateColumns and not element.gridTemplateRows then
-- No grid template defined, fall back to single column/row
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")
local rows = element.gridRows or 1
local columns = element.gridColumns or 1
-- Calculate available space
local availableWidth = element.width - element.padding.left - element.padding.right
local availableHeight = element.height - element.padding.top - element.padding.bottom
-- Resolve track sizes
-- Get gaps
local columnGap = element.columnGap or 0
local rowGap = element.rowGap or 0
local columnSizes = Grid.resolveTrackSizes(columnTracks, availableWidth, columnGap)
local rowSizes = Grid.resolveTrackSizes(rowTracks, availableHeight, rowGap)
-- Calculate cell sizes (equal distribution)
local totalColumnGaps = (columns - 1) * columnGap
local totalRowGaps = (rows - 1) * rowGap
local cellWidth = (availableWidth - totalColumnGaps) / columns
local cellHeight = (availableHeight - totalRowGaps) / rows
-- Calculate column and row positions
local columnPositions = {}
local rowPositions = {}
local currentX = element.x + element.padding.left
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
-- Get children that participate in grid layout
local gridChildren = {}
for _, child in ipairs(element.children) do
-- Skip explicitly absolute positioned children
if not (child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute) then
local placement = Grid.calculateItemPlacement(child, #columnSizes, #rowSizes, autoPlacementCursor, gridAutoFlow)
table.insert(gridChildren, child)
end
end
-- Ensure placement is within bounds, expand grid if necessary
local columnStart = math.max(1, math.min(placement.columnStart, #columnSizes + 1))
local columnEnd = math.max(columnStart + 1, math.min(placement.columnEnd, #columnSizes + 1))
local rowStart = math.max(1, math.min(placement.rowStart, #rowSizes + 1))
local rowEnd = math.max(rowStart + 1, math.min(placement.rowEnd, #rowSizes + 1))
-- Place children in grid cells
for i, child in ipairs(gridChildren) do
-- Calculate row and column (0-indexed for calculation)
local index = i - 1
local col = index % columns
local row = math.floor(index / columns)
-- Calculate item position and size
local itemX = columnPositions[columnStart] or element.x
local itemY = rowPositions[rowStart] or element.y
local itemWidth = (columnPositions[columnEnd] or (element.x + element.width)) - itemX - columnGap
local itemHeight = (rowPositions[rowEnd] or (element.y + element.height)) - itemY - rowGap
-- Skip if we've exceeded the grid
if row >= rows then
break
end
-- Apply alignment within grid cell
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))
local cellY = element.y + element.padding.top + (row * (cellHeight + rowGap))
-- Handle justifySelf (horizontal alignment)
if effectiveJustifySelf == GridJustifyItems.STRETCH or effectiveJustifySelf == "stretch" then
child.x = itemX + child.padding.left
child.width = itemWidth - child.padding.left - child.padding.right
elseif effectiveJustifySelf == GridJustifyItems.START or effectiveJustifySelf == "start" or effectiveJustifySelf == "flex-start" then
child.x = itemX + child.padding.left
-- Keep child's natural width
elseif effectiveJustifySelf == GridJustifyItems.END or effectiveJustifySelf == "end" or effectiveJustifySelf == "flex-end" then
child.x = itemX + itemWidth - child.width - child.padding.right
elseif effectiveJustifySelf == GridJustifyItems.CENTER or effectiveJustifySelf == "center" then
child.x = itemX + (itemWidth - child.width) / 2
-- Apply alignment within grid cell (default to stretch)
local effectiveAlignItems = element.alignItems or AlignItems.STRETCH
-- Stretch child to fill cell by default
if effectiveAlignItems == AlignItems.STRETCH or effectiveAlignItems == "stretch" then
child.x = cellX + child.padding.left
child.y = cellY + child.padding.top
child.width = cellWidth - child.padding.left - child.padding.right
child.height = cellHeight - child.padding.top - child.padding.bottom
-- Disable auto-sizing when stretched by grid
child.autosizing.width = false
child.autosizing.height = false
elseif effectiveAlignItems == AlignItems.CENTER or effectiveAlignItems == "center" then
child.x = cellX + (cellWidth - child.width) / 2
child.y = cellY + (cellHeight - child.height) / 2
elseif effectiveAlignItems == AlignItems.FLEX_START or effectiveAlignItems == "flex-start" or effectiveAlignItems == "start" then
child.x = cellX + child.padding.left
child.y = cellY + child.padding.top
elseif effectiveAlignItems == AlignItems.FLEX_END or effectiveAlignItems == "flex-end" or effectiveAlignItems == "end" then
child.x = cellX + cellWidth - child.width - child.padding.right
child.y = cellY + cellHeight - child.height - child.padding.bottom
else
-- Default to stretch
child.x = itemX + child.padding.left
child.width = itemWidth - child.padding.left - child.padding.right
end
-- Handle alignSelf (vertical alignment)
if effectiveAlignSelf == AlignItems.STRETCH or effectiveAlignSelf == "stretch" then
child.y = itemY + child.padding.top
child.height = itemHeight - child.padding.top - child.padding.bottom
elseif
effectiveAlignSelf == AlignItems.FLEX_START
or effectiveAlignSelf == "flex-start"
or effectiveAlignSelf == "start"
then
child.y = itemY + child.padding.top
-- Keep child's natural height
elseif
effectiveAlignSelf == AlignItems.FLEX_END
or effectiveAlignSelf == "flex-end"
or effectiveAlignSelf == "end"
then
child.y = itemY + itemHeight - child.height - child.padding.bottom
elseif effectiveAlignSelf == AlignItems.CENTER or effectiveAlignSelf == "center" then
child.y = itemY + (itemHeight - child.height) / 2
else
-- 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
child.x = cellX + child.padding.left
child.y = cellY + child.padding.top
child.width = cellWidth - child.padding.left - child.padding.right
child.height = cellHeight - child.padding.top - child.padding.bottom
-- Disable auto-sizing when stretched by grid
child.autosizing.width = false
child.autosizing.height = false
end
-- Layout child's children if it has any
@@ -719,7 +352,6 @@ function Grid.layoutGridItems(element)
child:layoutChildren()
end
end
end
end
--- Top level GUI manager
@@ -995,18 +627,10 @@ end
---@field transition TransitionProps -- Transition settings for animations
---@field callback function? -- Callback function for click events
---@field units table -- Original unit specifications for responsive behavior
---@field gridTemplateColumns string|table? -- Grid column track definitions
---@field gridTemplateRows string|table? -- Grid row track definitions
---@field gridAutoFlow GridAutoFlow? -- Grid auto-placement algorithm
---@field gridAutoColumns string? -- Size of auto-generated columns
---@field gridAutoRows string? -- Size of auto-generated rows
---@field gridRows number? -- Number of rows in the grid
---@field gridColumns number? -- Number of columns in the grid
---@field columnGap number|string? -- Gap between grid columns
---@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 = {}
Element.__index = Element
@@ -1046,17 +670,10 @@ Element.__index = Element
---@field callback function? -- Callback function for click events
---@field transform table? -- Transform properties for animations and styling
---@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 gridTemplateRows string|table? -- Grid row track definitions
---@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 gridRows number? -- Number of rows in the grid (default: 1)
---@field gridColumns number? -- Number of columns in the grid (default: 1)
---@field columnGap number|string? -- Gap between grid columns
---@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 = {}
---@param props ElementProps
@@ -1298,10 +915,7 @@ function Element.new(props)
end
end
-- Grid item properties (set early so they're available when addChild is called)
self.gridColumn = props.gridColumn
self.gridRow = props.gridRow
self.gridArea = props.gridArea
-- Grid properties are set later in the constructor
------ add hereditary ------
if props.parent == nil then
@@ -1362,12 +976,15 @@ function Element.new(props)
elseif props.positioning == Positioning.FLEX then
self.positioning = Positioning.FLEX
self._explicitlyAbsolute = false
elseif props.positioning == Positioning.GRID then
self.positioning = Positioning.GRID
self._explicitlyAbsolute = false
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
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
if self.parent.positioning == Positioning.FLEX or self.parent.positioning == Positioning.GRID then
self.positioning = Positioning.ABSOLUTE -- They are positioned BY flex/grid, not AS flex/grid
self._explicitlyAbsolute = false -- Participate in parent's layout
else
self.positioning = Positioning.ABSOLUTE
self._explicitlyAbsolute = false -- Default for absolute containers
@@ -1532,14 +1149,8 @@ function Element.new(props)
-- Grid container properties
if self.positioning == Positioning.GRID then
self.gridTemplateColumns = props.gridTemplateColumns
self.gridTemplateRows = props.gridTemplateRows
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.gridRows = props.gridRows or 1
self.gridColumns = props.gridColumns or 1
self.alignItems = props.alignItems or AlignItems.STRETCH
-- 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
-- Tests for CSS Grid layout functionality
-- Tests for simplified grid layout functionality
package.path = package.path .. ";?.lua"
@@ -22,37 +22,6 @@ function TestGridLayout:tearDown()
Gui.destroy()
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
-- ====================
@@ -64,13 +33,13 @@ function TestGridLayout:test_simple_grid_creation()
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr 1fr",
gridTemplateRows = "1fr 1fr",
gridRows = 2,
gridColumns = 3,
})
lu.assertEquals(grid.positioning, enums.Positioning.GRID)
lu.assertEquals(grid.gridTemplateColumns, "1fr 1fr 1fr")
lu.assertEquals(grid.gridTemplateRows, "1fr 1fr")
lu.assertEquals(grid.gridRows, 2)
lu.assertEquals(grid.gridColumns, 3)
end
function TestGridLayout:test_grid_with_gaps()
@@ -80,8 +49,8 @@ function TestGridLayout:test_grid_with_gaps()
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr",
gridTemplateRows = "1fr 1fr",
gridRows = 2,
gridColumns = 2,
columnGap = 10,
rowGap = 20,
})
@@ -97,8 +66,8 @@ function TestGridLayout:test_grid_auto_placement()
width = 300,
height = 200,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 100px 100px",
gridTemplateRows = "100px 100px",
gridRows = 2,
gridColumns = 3,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
@@ -127,75 +96,15 @@ function TestGridLayout:test_grid_auto_placement()
lu.assertAlmostEquals(items[4].y, 100, 1)
end
function TestGridLayout:test_grid_explicit_placement()
function TestGridLayout:test_grid_equal_distribution()
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 },
})
-- 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",
gridRows = 2,
gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
@@ -203,105 +112,52 @@ function TestGridLayout:test_fr_unit_distribution()
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,
})
-- First column should be 100px (1fr), second should be 200px (2fr)
lu.assertAlmostEquals(item1.x, 0, 1)
lu.assertAlmostEquals(item2.x, 100, 1)
lu.assertAlmostEquals(item1.width, 100, 1)
lu.assertAlmostEquals(item2.width, 200, 1)
-- Each cell should be 150x100 (300/2 x 200/2)
lu.assertAlmostEquals(item1.width, 150, 1)
lu.assertAlmostEquals(item1.height, 100, 1)
lu.assertAlmostEquals(item2.x, 150, 1)
lu.assertAlmostEquals(item2.width, 150, 1)
end
function TestGridLayout:test_mixed_units()
function TestGridLayout:test_grid_stretch_behavior()
local grid = Gui.new({
x = 0,
y = 0,
width = 400,
height = 200,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 1fr 2fr",
gridTemplateRows = "1fr",
gridRows = 1,
gridColumns = 3,
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 })
local item1 = Gui.new({ parent = grid, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, width = 50, height = 50 })
local item3 = Gui.new({ parent = grid, width = 50, height = 50 })
-- First column: 100px (fixed)
-- Remaining 300px divided as 1fr (100px) and 2fr (200px)
lu.assertAlmostEquals(item1.width, 100, 1)
lu.assertAlmostEquals(item2.width, 100, 1)
lu.assertAlmostEquals(item3.width, 200, 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
-- Each cell should be ~133.33px wide (400/3)
-- Items should stretch to fill cells
lu.assertAlmostEquals(item1.width, 133.33, 1)
lu.assertAlmostEquals(item2.width, 133.33, 1)
lu.assertAlmostEquals(item3.width, 133.33, 1)
end
-- ====================
-- 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()
local grid = Gui.new({
x = 0,
@@ -309,8 +165,8 @@ function TestGridLayout:test_align_items_stretch()
width = 300,
height = 200,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px",
gridTemplateRows = "100px 100px",
gridRows = 2,
gridColumns = 1,
alignItems = enums.AlignItems.STRETCH,
columnGap = 0,
rowGap = 0,
@@ -319,12 +175,10 @@ function TestGridLayout:test_align_items_stretch()
local item = Gui.new({
parent = grid,
gridColumn = 1,
gridRow = 1,
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)
end
@@ -339,17 +193,18 @@ function TestGridLayout:test_column_gap()
width = 320,
height = 100,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px 100px 100px",
gridTemplateRows = "100px",
gridRows = 1,
gridColumns = 3,
columnGap = 10,
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 })
local item1 = Gui.new({ parent = grid, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, 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(item2.x, 110, 1) -- 100 + 10 gap
lu.assertAlmostEquals(item3.x, 220, 1) -- 100 + 10 + 100 + 10
@@ -362,17 +217,18 @@ function TestGridLayout:test_row_gap()
width = 100,
height = 320,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "100px",
gridTemplateRows = "100px 100px 100px",
gridRows = 3,
gridColumns = 1,
columnGap = 0,
rowGap = 10,
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 = 1, gridRow = 2, width = 50, height = 50 })
local item3 = Gui.new({ parent = grid, gridColumn = 1, gridRow = 3, width = 50, height = 50 })
local item1 = Gui.new({ parent = grid, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, 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(item2.y, 110, 1) -- 100 + 10 gap
lu.assertAlmostEquals(item3.y, 220, 1) -- 100 + 10 + 100 + 10
@@ -389,8 +245,8 @@ function TestGridLayout:test_nested_grids()
width = 400,
height = 400,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr",
gridTemplateRows = "1fr 1fr",
gridRows = 2,
gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
@@ -398,29 +254,92 @@ function TestGridLayout:test_nested_grids()
local innerGrid = Gui.new({
parent = outerGrid,
gridColumn = 1,
gridRow = 1,
positioning = enums.Positioning.GRID,
gridTemplateColumns = "1fr 1fr",
gridTemplateRows = "1fr 1fr",
gridRows = 2,
gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local innerItem = Gui.new({
parent = innerGrid,
gridColumn = 2,
gridRow = 2,
-- Add items to inner grid
local item1 = Gui.new({ parent = innerGrid, width = 50, height = 50 })
local item2 = Gui.new({ parent = innerGrid, width = 50, height = 50 })
-- 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,
height = 50,
})
-- Inner grid should be in top-left quadrant (200x200)
-- Inner item should be in bottom-right of that (at 100, 100 relative to inner grid)
lu.assertAlmostEquals(innerItem.x, 100, 1)
lu.assertAlmostEquals(innerItem.y, 100, 1)
-- Item should stretch to fill the entire grid
lu.assertAlmostEquals(item.x, 0, 1)
lu.assertAlmostEquals(item.y, 0, 1)
lu.assertAlmostEquals(item.width, 100, 1)
lu.assertAlmostEquals(item.height, 100, 1)
end
print("Running Grid Layout Tests...")
print("Running Simplified Grid Layout Tests...")
os.exit(lu.LuaUnit.run())