simplified grid implementation
This commit is contained in:
563
FlexLove.lua
563
FlexLove.lua
@@ -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
|
||||||
|
|||||||
@@ -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")
|
|
||||||
@@ -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
236
examples/SimpleGrid.lua
Normal 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()
|
||||||
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user