diff --git a/FlexLove.lua b/FlexLove.lua index 8d5e892..4da28e0 100644 --- a/FlexLove.lua +++ b/FlexLove.lua @@ -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,423 +268,88 @@ 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 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 + + -- 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 + + -- 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) - - -- 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)) - - -- 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 - - -- 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 - - -- 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 - 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 - end - - -- Layout child's children if it has any - if #child.children > 0 then - child:layoutChildren() - end + table.insert(gridChildren, child) + end + end + + -- 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) + + -- Skip if we've exceeded the grid + if row >= rows then + break + end + + -- Calculate cell position + local cellX = element.x + element.padding.left + (col * (cellWidth + columnGap)) + local cellY = element.y + element.padding.top + (row * (cellHeight + rowGap)) + + -- 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 = 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 + if #child.children > 0 then + child:layoutChildren() end end end @@ -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 diff --git a/examples/BasicGrid.lua b/examples/BasicGrid.lua deleted file mode 100644 index a3ba9b1..0000000 --- a/examples/BasicGrid.lua +++ /dev/null @@ -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") diff --git a/examples/ResponsiveGrid.lua b/examples/ResponsiveGrid.lua deleted file mode 100644 index ca9d565..0000000 --- a/examples/ResponsiveGrid.lua +++ /dev/null @@ -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") diff --git a/examples/SimpleGrid.lua b/examples/SimpleGrid.lua new file mode 100644 index 0000000..aa8740b --- /dev/null +++ b/examples/SimpleGrid.lua @@ -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() diff --git a/testing/__tests__/15_grid_layout_tests.lua b/testing/__tests__/15_grid_layout_tests.lua index 8ca5dff..d98e3d4 100644 --- a/testing/__tests__/15_grid_layout_tests.lua +++ b/testing/__tests__/15_grid_layout_tests.lua @@ -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())