helium/layout/grid.lua
2021-07-03 21:13:03 +03:00

414 lines
10 KiB
Lua

local column = require "helium.layout.column"
--my copy of the cssssss grids
local path = string.sub(..., 1, string.len(...) - string.len(".grid"))
local layout = require(path..'.layout')
---@class GridCell
---@field name string @Will find the element with the relevant flag
---@alias GridRow GridCell[]
---@class HGridCell
---@field width number @Determines how wide this column will be
---@class WGridCell
---@field height string @Determines how high this column will be
---@alias HGridRow number[]|number|nil @Width of the row in cells
---@alias WGridCol number[]|number|nil @Width of the row in cells
---@alias GridLayout GridRow[]
---@class GridConfig
---@field layout GridLayout|nil @preconfigured layout table
---@field rows HGridRow|number|nil @set these instead of layout if you just want a regularly spaced 'table'
---@field columns WGridCol|number|nil @set these instead of layout if you just want a regularly spaced 'table' leave empty to flow in as many elements as you have
---@field verticalStretchMode "'stretch'"|"'normal'"
---@field horizontalStretchMode "'stretch'"|"'normal'"
---@field horizontalAlignMode "'left'"|"'center'"|"'right'"
---@field verticalAlignMode "'top'"|"'center'"|"'bottom'"
---@field rowSpacing number @size in pixels to space the rows
---@field colSpacing number @size in pixels to space the columns
---@field rowSizeMode "'relative'"|"'absolute'"
---@field colSizeMode "'relative'"|"'absolute'"
---@type GridConfig
local preconfiguredGrid = {
colSpacing = 20,
rowSpacing = 20,
verticalStretchMode = 'stretch',
horizontalStretchMode = 'stretch',
verticalAlignMode = 'center',
horizontalAlignMode = 'center',
rows = {5,5},
columns = {1,3,1},
layout = {
{'sidebar','content','sidebar2'},
{'sidebar','content','sidebar2'},
}
}
---@class grid
---@field gridLayout GridConfig
local grid = {}
grid.__index = grid
---@param gridLayout GridConfig
---@return layout
function grid.new(gridLayout)
local gridLayout = gridLayout or preconfiguredGrid
local self = setmetatable({
gridLayout = gridLayout
}, grid)
return layout(self, self.draw)
end
local function alignLeft(x, wroot, wchild)
return x
end
local function alignCenter(x, wroot, wchild)
return x+(wroot/2-wchild/2)
end
local function alignRight(x, wroot, wchild)
return x+(wroot-wchild)
end
local function alignHandlerX(mode, x, wr, wc)
if mode == 'center' then
return alignCenter(x, wr, wc)
elseif mode == 'right' then
return alignRight(x, wr, wc)
else
return alignLeft(x)
end
end
local function alignHandlerY(mode, y, hr, hc)
if mode == 'center' then
return alignCenter(y, hr, hc)
elseif mode == 'bottom' then
return alignRight(y, hr, hc)
else
return alignLeft(y)
end
end
function grid:draw(xRoot, yRoot, width, height, children)
-- Either of these means no named layout
local fullyAutoLayout = false
local autoCols = false
local autoRows = false
local equalRows = false
local equalCols = false
local vertValueToPixels = 0
local horValueToPixels = 0
local XIndexes = {}
local YIndexes = {}
if self.gridLayout.columns then
if not self.gridLayout.rows then
autoRows = true
else
if type(self.gridLayout.rows)=="table" then
local total = 0
for i, col in ipairs(self.gridLayout.rows) do
YIndexes[i] = {}
YIndexes[i].start = total
total = total + col
YIndexes[i].finish = total
end
vertValueToPixels = height/total
else
vertValueToPixels = height/self.gridLayout.rows
for y = 1, self.gridLayout.rows do
YIndexes[y] = {}
YIndexes[y].start = y-1
YIndexes[y].finish = y
end
equalRows = true
end
end
if type(self.gridLayout.columns)=="table" then
local total = 0
for i, col in ipairs(self.gridLayout.columns) do
XIndexes[i] = {}
XIndexes[i].start = total
total = total + col
XIndexes[i].finish = total
end
horValueToPixels = width/total
else
horValueToPixels = width/self.gridLayout.columns
for x = 1, self.gridLayout.columns do
XIndexes[x] = {}
XIndexes[x].start = x-1
XIndexes[x].finish = x
end
equalCols = true
end
else
if not self.gridLayout.rows then
fullyAutoLayout = true
autoRows = true
else
autoCols = true
if type(self.gridLayout.rows)=="table" then
local total = 0
for i, col in ipairs(self.gridLayout.rows) do
total = total + col
end
vertValueToPixels = (height)/total
else
vertValueToPixels = (height)/self.gridLayout.rows
equalRows = true
end
end
end
if (not autoRows) and (not autoCols) then
if self.gridLayout.layout then
local layout = {}
--flip layout table
for x = 1, #self.gridLayout.layout[1] do
layout[x] = {}
end
for x = 1, #self.gridLayout.layout do
for y = 1, #self.gridLayout.layout[x] do
layout[y][x] = self.gridLayout.layout[x][y]
end
end
local layoutDepth, layoutWidth = #self.gridLayout.layout, #layout
if type(self.gridLayout.rows) == "table" then
if not #self.gridLayout.rows == layoutDepth then
error('Layout table doesnt match row number')
end
else
if not self.gridLayout.rows == layoutDepth then
error('Layout table doesnt match row number')
end
end
if type(self.gridLayout.columns) == "table" then
if not #self.gridLayout.columns == layoutWidth then
error('Layout table doesnt match column number')
end
else
if not self.gridLayout.columns == layoutWidth then
error('Layout table doesnt match column number')
end
end
-- {x, y, width, height}
local fields = {
}
for x = 1, #layout do
for y = 1, #layout[x] do
if not fields[layout[x][y]] then
local finishedRow = false
local finishedCol = false
local curField = layout[x][y]
fields[curField] = {}
fields[curField].x = XIndexes[x].start
fields[curField].y = YIndexes[y].start
fields[curField].finX = XIndexes[x].finish
fields[curField].finY = YIndexes[y].finish
local parseX, parseY = x+1, y+1
while not finishedRow do
if layout[x][parseY] and layout[x][parseY] == curField then
fields[curField].finY = YIndexes[parseY].finish
else
finishedRow = true
end
parseY = parseY + 1
end
while not finishedCol do
if layout[parseX] and layout[parseX][y] == curField then
fields[curField].finX = XIndexes[parseX].finish
else
finishedCol = true
end
parseX = parseX + 1
end
fields[curField].h = fields[curField].finY - fields[curField].y
fields[curField].w = fields[curField].finX - fields[curField].x
end
end
end
for i, field in pairs(fields) do
for y, elem in ipairs(children) do
if elem.flags[i] then
field.element = elem
end
end
end
for i, field in pairs(fields) do
if field.element then
local containerWidth = (field.w*horValueToPixels)-(self.gridLayout.colSpacing)
local containerHeight = (field.h*vertValueToPixels)-(self.gridLayout.rowSpacing)
local containerX = ((field.x)*horValueToPixels)+(self.gridLayout.colSpacing/2)
local containerY = ((field.y)*vertValueToPixels)+(self.gridLayout.rowSpacing/2)
local w, h = field.element:getSize()
local x, y
if self.gridLayout.horizontalStretchMode =='stretch' then
w = containerWidth
x = containerX
else
x = alignHandlerX(self.gridLayout.horizontalAlignMode, containerX, containerWidth, w)
end
if self.gridLayout.verticalStretchMode =='stretch' then
h = containerHeight
y = containerY
else
y = alignHandlerY(self.gridLayout.verticalAlignMode, containerY, containerHeight, h)
end
field.element:draw(x+xRoot, y+yRoot, w, h)
end
end
else
error('please provide a layout table')
end
elseif fullyAutoLayout then--one element per width, vertically down
local carriagePos = 0
if children then
for i, e in ipairs(children) do
local w, h = e:getSize()
if self.gridLayout.horizontalStretchMode =='stretch' then
w = width
e:draw(xRoot, yRoot+carriagePos, w)
else
local x = alignHandlerX(self.gridLayout.horizontalAlignMode, xRoot, width, w)
e:draw(x, yRoot+carriagePos)
end
carriagePos = carriagePos + self.gridLayout.rowSpacing + h
end
end
elseif autoCols then--one element per width, rows spaced
local carriagePos = 0
local row = 1
local lastRowSize = 1
if children then
for i, e in ipairs(children) do
local w, h = e:getSize()
local rowSize
local x, y
if equalRows then
rowSize = 1 * vertValueToPixels
else
rowSize = (self.gridLayout.rows[row] or lastRowSize)*vertValueToPixels
end
rowSize = math.max(h, rowSize)
if self.gridLayout.horizontalStretchMode =='stretch' then
w = width
x = xRoot
else
x = alignHandlerX(self.gridLayout.horizontalAlignMode, xRoot, width, w)
end
if self.gridLayout.verticalStretchMode =='stretch' then
h = rowSize
y = carriagePos
else
y = alignHandlerY(self.gridLayout.verticalAlignMode, carriagePos, rowSize, h)
end
e:draw(x, y + yRoot, w, h)
carriagePos = carriagePos + self.gridLayout.rowSpacing + rowSize
row = row + 1
end
end
elseif autoRows then--flow the elements freely vertically, space columns according to layout
local carriagePos = 0
local row = 1
local colDrawStart = 0
local currentRowMax = 1
local currentCol = 1
local rowWidth
if equalCols then
rowWidth = self.gridLayout.columns
else
rowWidth = #self.gridLayout.columns
end
if children then
for i, e in ipairs(children) do
local w, h = e:getSize()
local colSize
local x, y
if equalCols then
colSize = 1 * horValueToPixels
else
colSize = self.gridLayout.columns[currentCol] * horValueToPixels
end
currentRowMax = math.max(currentRowMax, h)
if self.gridLayout.horizontalStretchMode =='stretch' then
w = colSize
x = colDrawStart
else
x = alignHandlerX(self.gridLayout.horizontalAlignMode, colDrawStart, colSize, w)
end
e:draw(x, yRoot+carriagePos, w, h)
colDrawStart = colDrawStart + colSize + self.gridLayout.colSpacing
if currentCol == rowWidth then
carriagePos = carriagePos + self.gridLayout.rowSpacing + currentRowMax
currentCol = 0
currentRowMax = 0
colDrawStart = 0
end
currentCol = currentCol + 1
end
end
end
end
return grid