Fixed a bug in reactive not calling the observers
Fixed a layout issue (not updating properly) added the flow layout
This commit is contained in:
193
layouts/flow.lua
Normal file
193
layouts/flow.lua
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
local flow = {}
|
||||||
|
|
||||||
|
--- Calculates positions for all children in a flow layout
|
||||||
|
--- @param instance table The layout instance
|
||||||
|
--- - container: the container to layout
|
||||||
|
--- - options: layout options
|
||||||
|
--- - direction: "horizontal" or "vertical" (default: "horizontal")
|
||||||
|
--- - spacing: gap between elements (default: 0)
|
||||||
|
--- - padding: padding around the flow (default: 0)
|
||||||
|
--- - align: "start", "center", or "end" (default: "start")
|
||||||
|
function flow.calculate(instance)
|
||||||
|
local container = instance.container
|
||||||
|
local options = instance.options or {}
|
||||||
|
|
||||||
|
local children = container.get("children")
|
||||||
|
local containerWidth = container.get("width")
|
||||||
|
local containerHeight = container.get("height")
|
||||||
|
|
||||||
|
local direction = options.direction or "horizontal"
|
||||||
|
local spacing = options.spacing or 0
|
||||||
|
local padding = options.padding or 0
|
||||||
|
local align = options.align or "start"
|
||||||
|
|
||||||
|
local childCount = #children
|
||||||
|
if childCount == 0 then
|
||||||
|
instance._positions = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local positions = {}
|
||||||
|
|
||||||
|
if direction == "horizontal" then
|
||||||
|
local rows = {}
|
||||||
|
local currentRow = {}
|
||||||
|
local currentX = padding + 1
|
||||||
|
local currentY = padding + 1
|
||||||
|
local maxHeightInRow = 0
|
||||||
|
|
||||||
|
for i, child in ipairs(children) do
|
||||||
|
local childWidth = child.get("width")
|
||||||
|
local childHeight = child.get("height")
|
||||||
|
|
||||||
|
if currentX + childWidth - 1 > containerWidth - padding and currentX > padding + 1 then
|
||||||
|
|
||||||
|
table.insert(rows, {
|
||||||
|
children = currentRow,
|
||||||
|
y = currentY,
|
||||||
|
height = maxHeightInRow
|
||||||
|
})
|
||||||
|
|
||||||
|
currentRow = {}
|
||||||
|
currentX = padding + 1
|
||||||
|
currentY = currentY + maxHeightInRow + spacing
|
||||||
|
maxHeightInRow = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(currentRow, {
|
||||||
|
child = child,
|
||||||
|
width = childWidth,
|
||||||
|
height = childHeight
|
||||||
|
})
|
||||||
|
|
||||||
|
currentX = currentX + childWidth + spacing
|
||||||
|
maxHeightInRow = math.max(maxHeightInRow, childHeight)
|
||||||
|
end
|
||||||
|
|
||||||
|
if #currentRow > 0 then
|
||||||
|
table.insert(rows, {
|
||||||
|
children = currentRow,
|
||||||
|
y = currentY,
|
||||||
|
height = maxHeightInRow
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, row in ipairs(rows) do
|
||||||
|
local rowWidth = 0
|
||||||
|
for j, item in ipairs(row.children) do
|
||||||
|
rowWidth = rowWidth + item.width
|
||||||
|
if j < #row.children then
|
||||||
|
rowWidth = rowWidth + spacing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local startX = padding + 1
|
||||||
|
if align == "center" then
|
||||||
|
startX = padding + 1 + math.floor((containerWidth - 2 * padding - rowWidth) / 2)
|
||||||
|
elseif align == "end" then
|
||||||
|
startX = containerWidth - padding - rowWidth + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = startX
|
||||||
|
for _, item in ipairs(row.children) do
|
||||||
|
local y = row.y
|
||||||
|
if align == "center" then
|
||||||
|
y = row.y + math.floor((row.height - item.height) / 2)
|
||||||
|
elseif align == "end" then
|
||||||
|
y = row.y + row.height - item.height
|
||||||
|
end
|
||||||
|
|
||||||
|
positions[item.child] = {
|
||||||
|
x = x,
|
||||||
|
y = y,
|
||||||
|
width = item.width,
|
||||||
|
height = item.height
|
||||||
|
}
|
||||||
|
|
||||||
|
x = x + item.width + spacing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
local columns = {}
|
||||||
|
local currentColumn = {}
|
||||||
|
local currentX = padding + 1
|
||||||
|
local currentY = padding + 1
|
||||||
|
local maxWidthInColumn = 0
|
||||||
|
|
||||||
|
for i, child in ipairs(children) do
|
||||||
|
local childWidth = child.get("width")
|
||||||
|
local childHeight = child.get("height")
|
||||||
|
|
||||||
|
if currentY + childHeight - 1 > containerHeight - padding and currentY > padding + 1 then
|
||||||
|
table.insert(columns, {
|
||||||
|
children = currentColumn,
|
||||||
|
x = currentX,
|
||||||
|
width = maxWidthInColumn
|
||||||
|
})
|
||||||
|
|
||||||
|
currentColumn = {}
|
||||||
|
currentY = padding + 1
|
||||||
|
currentX = currentX + maxWidthInColumn + spacing
|
||||||
|
maxWidthInColumn = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(currentColumn, {
|
||||||
|
child = child,
|
||||||
|
width = childWidth,
|
||||||
|
height = childHeight
|
||||||
|
})
|
||||||
|
|
||||||
|
currentY = currentY + childHeight + spacing
|
||||||
|
maxWidthInColumn = math.max(maxWidthInColumn, childWidth)
|
||||||
|
end
|
||||||
|
|
||||||
|
if #currentColumn > 0 then
|
||||||
|
table.insert(columns, {
|
||||||
|
children = currentColumn,
|
||||||
|
x = currentX,
|
||||||
|
width = maxWidthInColumn
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, column in ipairs(columns) do
|
||||||
|
local columnHeight = 0
|
||||||
|
for j, item in ipairs(column.children) do
|
||||||
|
columnHeight = columnHeight + item.height
|
||||||
|
if j < #column.children then
|
||||||
|
columnHeight = columnHeight + spacing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local startY = padding + 1
|
||||||
|
if align == "center" then
|
||||||
|
startY = padding + 1 + math.floor((containerHeight - 2 * padding - columnHeight) / 2)
|
||||||
|
elseif align == "end" then
|
||||||
|
startY = containerHeight - padding - columnHeight + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local y = startY
|
||||||
|
for _, item in ipairs(column.children) do
|
||||||
|
local x = column.x
|
||||||
|
if align == "center" then
|
||||||
|
x = column.x + math.floor((column.width - item.width) / 2)
|
||||||
|
elseif align == "end" then
|
||||||
|
x = column.x + column.width - item.width
|
||||||
|
end
|
||||||
|
|
||||||
|
positions[item.child] = {
|
||||||
|
x = x,
|
||||||
|
y = y,
|
||||||
|
width = item.width,
|
||||||
|
height = item.height
|
||||||
|
}
|
||||||
|
|
||||||
|
y = y + item.height + spacing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
instance._positions = positions
|
||||||
|
end
|
||||||
|
|
||||||
|
return flow
|
||||||
@@ -22,6 +22,11 @@ function grid.calculate(instance)
|
|||||||
local columns = options.columns
|
local columns = options.columns
|
||||||
|
|
||||||
local childCount = #children
|
local childCount = #children
|
||||||
|
if childCount == 0 then
|
||||||
|
instance._positions = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if not rows and not columns then
|
if not rows and not columns then
|
||||||
columns = math.ceil(math.sqrt(childCount))
|
columns = math.ceil(math.sqrt(childCount))
|
||||||
rows = math.ceil(childCount / columns)
|
rows = math.ceil(childCount / columns)
|
||||||
@@ -31,11 +36,21 @@ function grid.calculate(instance)
|
|||||||
rows = math.ceil(childCount / columns)
|
rows = math.ceil(childCount / columns)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if columns <= 0 then columns = 1 end
|
||||||
|
if rows <= 0 then rows = 1 end
|
||||||
|
|
||||||
local availableWidth = containerWidth - (2 * padding) - ((columns - 1) * spacing)
|
local availableWidth = containerWidth - (2 * padding) - ((columns - 1) * spacing)
|
||||||
local availableHeight = containerHeight - (2 * padding) - ((rows - 1) * spacing)
|
local availableHeight = containerHeight - (2 * padding) - ((rows - 1) * spacing)
|
||||||
|
|
||||||
|
if availableWidth < 1 then availableWidth = 1 end
|
||||||
|
if availableHeight < 1 then availableHeight = 1 end
|
||||||
|
|
||||||
local cellWidth = math.floor(availableWidth / columns)
|
local cellWidth = math.floor(availableWidth / columns)
|
||||||
local cellHeight = math.floor(availableHeight / rows)
|
local cellHeight = math.floor(availableHeight / rows)
|
||||||
|
|
||||||
|
if cellWidth < 1 then cellWidth = 1 end
|
||||||
|
if cellHeight < 1 then cellHeight = 1 end
|
||||||
|
|
||||||
local positions = {}
|
local positions = {}
|
||||||
|
|
||||||
for i, child in ipairs(children) do
|
for i, child in ipairs(children) do
|
||||||
|
|||||||
@@ -103,10 +103,12 @@ function Container:init(props, basalt)
|
|||||||
self:observe("width", function()
|
self:observe("width", function()
|
||||||
self.set("childrenSorted", false)
|
self.set("childrenSorted", false)
|
||||||
self.set("childrenEventsSorted", false)
|
self.set("childrenEventsSorted", false)
|
||||||
|
self:updateRender()
|
||||||
end)
|
end)
|
||||||
self:observe("height", function()
|
self:observe("height", function()
|
||||||
self.set("childrenSorted", false)
|
self.set("childrenSorted", false)
|
||||||
self.set("childrenEventsSorted", false)
|
self.set("childrenEventsSorted", false)
|
||||||
|
self:updateRender()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -203,11 +205,12 @@ end
|
|||||||
--- @shortDescription Updates child element ordering
|
--- @shortDescription Updates child element ordering
|
||||||
--- @return Container self For method chaining
|
--- @return Container self For method chaining
|
||||||
function Container:sortChildren()
|
function Container:sortChildren()
|
||||||
self.set("visibleChildren", sortAndFilterChildren(self, self._values.children))
|
|
||||||
self.set("childrenSorted", true)
|
self.set("childrenSorted", true)
|
||||||
if self._layoutInstance then
|
if self._layoutInstance then
|
||||||
self:updateLayout()
|
self:updateLayout()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self.set("visibleChildren", sortAndFilterChildren(self, self._values.children))
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -191,6 +191,14 @@ local observerCache = setmetatable({}, {
|
|||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
|
local valueCache = setmetatable({}, {
|
||||||
|
__mode = "k",
|
||||||
|
__index = function(t, k)
|
||||||
|
t[k] = {}
|
||||||
|
return t[k]
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
local function setupObservers(element, expr, propertyName)
|
local function setupObservers(element, expr, propertyName)
|
||||||
local deps = analyzeDependencies(expr)
|
local deps = analyzeDependencies(expr)
|
||||||
|
|
||||||
@@ -229,7 +237,20 @@ local function setupObservers(element, expr, propertyName)
|
|||||||
target = target,
|
target = target,
|
||||||
property = isState and "states" or prop,
|
property = isState and "states" or prop,
|
||||||
callback = function()
|
callback = function()
|
||||||
element:updateRender()
|
local oldValue = valueCache[element][propertyName]
|
||||||
|
local newValue = element.get(propertyName)
|
||||||
|
|
||||||
|
if oldValue ~= newValue then
|
||||||
|
valueCache[element][propertyName] = newValue
|
||||||
|
|
||||||
|
if element._observers and element._observers[propertyName] then
|
||||||
|
for _, obs in ipairs(element._observers[propertyName]) do
|
||||||
|
obs()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
element:updateRender()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
target:observe(observer.property, observer.callback)
|
target:observe(observer.property, observer.callback)
|
||||||
@@ -281,6 +302,8 @@ PropertySystem.addSetterHook(function(element, propertyName, value, config)
|
|||||||
end
|
end
|
||||||
return config.default
|
return config.default
|
||||||
end
|
end
|
||||||
|
|
||||||
|
valueCache[element][propertyName] = result
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -304,6 +327,7 @@ BaseElement.hooks = {
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
observerCache[self] = nil
|
observerCache[self] = nil
|
||||||
|
valueCache[self] = nil
|
||||||
functionCache[self] = nil
|
functionCache[self] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user