fixed layout manager so that we know if size got manually changed
fixed scrollframe not sending scroll events to its children fixed scrollframe scrolling even if mouse is not hovering over the element improved the behaviour of the flow layout
This commit is contained in:
125
layouts/flow.lua
125
layouts/flow.lua
@@ -39,8 +39,24 @@ function flow.calculate(instance)
|
||||
for i, child in ipairs(children) do
|
||||
local childWidth = child.get("width")
|
||||
local childHeight = child.get("height")
|
||||
local layoutConfig = child.get("layoutConfig") or {}
|
||||
|
||||
if currentX + childWidth - 1 > containerWidth - padding and currentX > padding + 1 then
|
||||
if child._userModified then
|
||||
child._originalWidth = childWidth
|
||||
child._originalHeight = childHeight
|
||||
end
|
||||
|
||||
local basis = layoutConfig.basis
|
||||
if not basis then
|
||||
if not child._originalWidth then
|
||||
child._originalWidth = childWidth
|
||||
end
|
||||
basis = child._originalWidth
|
||||
end
|
||||
|
||||
local hasFlexConfig = layoutConfig.grow or layoutConfig.shrink or layoutConfig.basis
|
||||
|
||||
if currentX + basis - 1 > containerWidth - padding and currentX > padding + 1 then
|
||||
|
||||
table.insert(rows, {
|
||||
children = currentRow,
|
||||
@@ -57,10 +73,14 @@ function flow.calculate(instance)
|
||||
table.insert(currentRow, {
|
||||
child = child,
|
||||
width = childWidth,
|
||||
height = childHeight
|
||||
height = childHeight,
|
||||
basis = basis,
|
||||
grow = layoutConfig.grow or 0,
|
||||
shrink = layoutConfig.shrink or 1,
|
||||
hasFlexConfig = hasFlexConfig
|
||||
})
|
||||
|
||||
currentX = currentX + childWidth + spacing
|
||||
currentX = currentX + basis + spacing
|
||||
maxHeightInRow = math.max(maxHeightInRow, childHeight)
|
||||
end
|
||||
|
||||
@@ -73,9 +93,39 @@ function flow.calculate(instance)
|
||||
end
|
||||
|
||||
for _, row in ipairs(rows) do
|
||||
local totalBasis = 0
|
||||
local totalSpacing = (#row.children - 1) * spacing
|
||||
local totalGrow = 0
|
||||
local totalShrink = 0
|
||||
|
||||
for _, item in ipairs(row.children) do
|
||||
totalBasis = totalBasis + item.basis
|
||||
totalGrow = totalGrow + item.grow
|
||||
totalShrink = totalShrink + item.shrink
|
||||
end
|
||||
|
||||
local availableWidth = containerWidth - 2 * padding
|
||||
local remainingSpace = availableWidth - totalBasis - totalSpacing
|
||||
|
||||
for _, item in ipairs(row.children) do
|
||||
if remainingSpace > 0 and totalGrow > 0 then
|
||||
local extraSpace = (remainingSpace * item.grow) / totalGrow
|
||||
item.finalWidth = item.basis + extraSpace
|
||||
elseif remainingSpace < 0 and totalShrink > 0 then
|
||||
local reduceSpace = (-remainingSpace * item.shrink) / totalShrink
|
||||
item.finalWidth = math.max(1, item.basis - reduceSpace)
|
||||
else
|
||||
item.finalWidth = item.basis
|
||||
end
|
||||
|
||||
if not item.hasFlexConfig then
|
||||
item.finalWidth = item.basis
|
||||
end
|
||||
end
|
||||
|
||||
local rowWidth = 0
|
||||
for j, item in ipairs(row.children) do
|
||||
rowWidth = rowWidth + item.width
|
||||
rowWidth = rowWidth + item.finalWidth
|
||||
if j < #row.children then
|
||||
rowWidth = rowWidth + spacing
|
||||
end
|
||||
@@ -100,11 +150,11 @@ function flow.calculate(instance)
|
||||
positions[item.child] = {
|
||||
x = x,
|
||||
y = y,
|
||||
width = item.width,
|
||||
width = math.floor(item.finalWidth),
|
||||
height = item.height
|
||||
}
|
||||
|
||||
x = x + item.width + spacing
|
||||
x = x + math.floor(item.finalWidth) + spacing
|
||||
end
|
||||
end
|
||||
|
||||
@@ -118,8 +168,25 @@ function flow.calculate(instance)
|
||||
for i, child in ipairs(children) do
|
||||
local childWidth = child.get("width")
|
||||
local childHeight = child.get("height")
|
||||
local layoutConfig = child.get("layoutConfig") or {}
|
||||
|
||||
if currentY + childHeight - 1 > containerHeight - padding and currentY > padding + 1 then
|
||||
-- If user modified the element, update the original size
|
||||
if child._userModified then
|
||||
child._originalWidth = childWidth
|
||||
child._originalHeight = childHeight
|
||||
end
|
||||
|
||||
local basis = layoutConfig.basis
|
||||
if not basis then
|
||||
if not child._originalHeight then
|
||||
child._originalHeight = childHeight
|
||||
end
|
||||
basis = child._originalHeight
|
||||
end
|
||||
|
||||
local hasFlexConfig = layoutConfig.grow or layoutConfig.shrink or layoutConfig.basis
|
||||
|
||||
if currentY + basis - 1 > containerHeight - padding and currentY > padding + 1 then
|
||||
table.insert(columns, {
|
||||
children = currentColumn,
|
||||
x = currentX,
|
||||
@@ -135,10 +202,14 @@ function flow.calculate(instance)
|
||||
table.insert(currentColumn, {
|
||||
child = child,
|
||||
width = childWidth,
|
||||
height = childHeight
|
||||
height = childHeight,
|
||||
basis = basis,
|
||||
grow = layoutConfig.grow or 0,
|
||||
shrink = layoutConfig.shrink or 1,
|
||||
hasFlexConfig = hasFlexConfig
|
||||
})
|
||||
|
||||
currentY = currentY + childHeight + spacing
|
||||
currentY = currentY + basis + spacing
|
||||
maxWidthInColumn = math.max(maxWidthInColumn, childWidth)
|
||||
end
|
||||
|
||||
@@ -151,9 +222,39 @@ function flow.calculate(instance)
|
||||
end
|
||||
|
||||
for _, column in ipairs(columns) do
|
||||
local totalBasis = 0
|
||||
local totalSpacing = (#column.children - 1) * spacing
|
||||
local totalGrow = 0
|
||||
local totalShrink = 0
|
||||
|
||||
for _, item in ipairs(column.children) do
|
||||
totalBasis = totalBasis + item.basis
|
||||
totalGrow = totalGrow + item.grow
|
||||
totalShrink = totalShrink + item.shrink
|
||||
end
|
||||
|
||||
local availableHeight = containerHeight - 2 * padding
|
||||
local remainingSpace = availableHeight - totalBasis - totalSpacing
|
||||
|
||||
for _, item in ipairs(column.children) do
|
||||
if remainingSpace > 0 and totalGrow > 0 then
|
||||
local extraSpace = (remainingSpace * item.grow) / totalGrow
|
||||
item.finalHeight = item.basis + extraSpace
|
||||
elseif remainingSpace < 0 and totalShrink > 0 then
|
||||
local reduceSpace = (-remainingSpace * item.shrink) / totalShrink
|
||||
item.finalHeight = math.max(1, item.basis - reduceSpace)
|
||||
else
|
||||
item.finalHeight = item.basis
|
||||
end
|
||||
|
||||
if not item.hasFlexConfig then
|
||||
item.finalHeight = item.basis
|
||||
end
|
||||
end
|
||||
|
||||
local columnHeight = 0
|
||||
for j, item in ipairs(column.children) do
|
||||
columnHeight = columnHeight + item.height
|
||||
columnHeight = columnHeight + item.finalHeight
|
||||
if j < #column.children then
|
||||
columnHeight = columnHeight + spacing
|
||||
end
|
||||
@@ -179,10 +280,10 @@ function flow.calculate(instance)
|
||||
x = x,
|
||||
y = y,
|
||||
width = item.width,
|
||||
height = item.height
|
||||
height = math.floor(item.finalHeight)
|
||||
}
|
||||
|
||||
y = y + item.height + spacing
|
||||
y = y + math.floor(item.finalHeight) + spacing
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local elementManager = require("elementManager")
|
||||
local errorManager = require("errorManager")
|
||||
local VisualElement = elementManager.getElement("VisualElement")
|
||||
local LayoutManager = require("layoutManager")
|
||||
local expect = require("libraries/expect")
|
||||
local split = require("libraries/utils").split
|
||||
---@configDescription The container class. It is a visual element that can contain other elements. It is the base class for all containers
|
||||
@@ -697,7 +698,6 @@ end
|
||||
--- @param options? table Optional layout-specific options
|
||||
--- @return Container self For method chaining
|
||||
function Container:applyLayout(layoutPath, options)
|
||||
local LayoutManager = require("layoutManager")
|
||||
|
||||
if self._layoutInstance then
|
||||
LayoutManager.destroy(self._layoutInstance)
|
||||
@@ -716,7 +716,6 @@ end
|
||||
--- @return Container self For method chaining
|
||||
function Container:updateLayout()
|
||||
if self._layoutInstance then
|
||||
local LayoutManager = require("layoutManager")
|
||||
LayoutManager.update(self._layoutInstance)
|
||||
end
|
||||
return self
|
||||
|
||||
@@ -320,20 +320,40 @@ end
|
||||
--- @return boolean Whether the event was handled
|
||||
--- @protected
|
||||
function ScrollFrame:mouse_scroll(direction, x, y)
|
||||
if self:isInBounds(x, y) then
|
||||
local xOffset, yOffset = self.get("offsetX"), self.get("offsetY")
|
||||
local relX, relY = self:getRelativePosition(x + xOffset, y + yOffset)
|
||||
|
||||
local success, child = self:callChildrenEvent(true, "mouse_scroll", direction, relX, relY)
|
||||
if success then
|
||||
return true
|
||||
end
|
||||
|
||||
local height = self.get("height")
|
||||
local width = self.get("width")
|
||||
local offsetY = self.get("offsetY")
|
||||
local offsetX = self.get("offsetX")
|
||||
local contentWidth = self.get("contentWidth")
|
||||
local contentHeight = self.get("contentHeight")
|
||||
|
||||
local needsHorizontalScrollBar = self.get("showScrollBar") and contentWidth > width
|
||||
local viewportHeight = needsHorizontalScrollBar and height - 1 or height
|
||||
local maxScroll = math.max(0, contentHeight - viewportHeight)
|
||||
local needsVerticalScrollBar = self.get("showScrollBar") and contentHeight > viewportHeight
|
||||
local viewportWidth = needsVerticalScrollBar and width - 1 or width
|
||||
|
||||
if needsVerticalScrollBar then
|
||||
local maxScroll = math.max(0, contentHeight - viewportHeight)
|
||||
local newScroll = math.min(maxScroll, math.max(0, offsetY + direction))
|
||||
self.set("offsetY", newScroll)
|
||||
elseif needsHorizontalScrollBar then
|
||||
local maxScroll = math.max(0, contentWidth - viewportWidth)
|
||||
local newScroll = math.min(maxScroll, math.max(0, offsetX + direction))
|
||||
self.set("offsetX", newScroll)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Renders the ScrollFrame and its scrollbars
|
||||
|
||||
@@ -64,6 +64,9 @@ end})
|
||||
---@property ignoreOffset boolean false Whether to ignore the parent's offset
|
||||
VisualElement.defineProperty(VisualElement, "ignoreOffset", {default = false, type = "boolean"})
|
||||
|
||||
---@property layoutConfig table {} Configuration for layout systems (grow, shrink, alignSelf, etc.)
|
||||
VisualElement.defineProperty(VisualElement, "layoutConfig", {default = {}, type = "table"})
|
||||
|
||||
---@combinedProperty position {x number, y number} Combined x, y position
|
||||
VisualElement.combineProperties(VisualElement, "position", "x", "y")
|
||||
---@combinedProperty size {width number, height number} Combined width, height
|
||||
@@ -178,6 +181,27 @@ function VisualElement:setConstraint(property, targetElement, targetProperty, of
|
||||
return self
|
||||
end
|
||||
|
||||
--- Updates a single property in the layoutConfig table
|
||||
--- @shortDescription Updates a single layout config property without replacing the entire table
|
||||
--- @param key string The layout config property to update (grow, shrink, basis, alignSelf, order, etc.)
|
||||
--- @param value any The value to set for the property
|
||||
--- @return VisualElement self The element instance
|
||||
function VisualElement:setLayoutConfigProperty(key, value)
|
||||
local layoutConfig = self.get("layoutConfig")
|
||||
layoutConfig[key] = value
|
||||
self.set("layoutConfig", layoutConfig)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets a single property from the layoutConfig table
|
||||
--- @shortDescription Gets a single layout config property
|
||||
--- @param key string The layout config property to get
|
||||
--- @return any value The value of the property, or nil if not set
|
||||
function VisualElement:getLayoutConfigProperty(key)
|
||||
local layoutConfig = self.get("layoutConfig")
|
||||
return layoutConfig[key]
|
||||
end
|
||||
|
||||
--- Resolves all constraints for the element
|
||||
--- @shortDescription Resolves all constraints for the element
|
||||
--- @return VisualElement self The element instance
|
||||
@@ -191,7 +215,7 @@ function VisualElement:resolveAllConstraints()
|
||||
for _, property in ipairs(order) do
|
||||
if constraints[property] then
|
||||
local value = self:_resolveConstraint(property, constraints[property])
|
||||
self:_applyConstraintValue(property, value)
|
||||
self:_applyConstraintValue(property, value, constraints)
|
||||
end
|
||||
end
|
||||
self._constraintsDirty = false
|
||||
@@ -200,17 +224,31 @@ end
|
||||
|
||||
--- Applies a resolved constraint value to the appropriate property
|
||||
--- @private
|
||||
function VisualElement:_applyConstraintValue(property, value)
|
||||
function VisualElement:_applyConstraintValue(property, value, constraints)
|
||||
if property == "x" or property == "left" then
|
||||
self.set("x", value)
|
||||
elseif property == "y" or property == "top" then
|
||||
self.set("y", value)
|
||||
elseif property == "right" then
|
||||
if constraints.left then
|
||||
local leftValue = self:_resolveConstraint("left", constraints.left)
|
||||
local width = value - leftValue + 1
|
||||
self.set("width", width)
|
||||
self.set("x", leftValue)
|
||||
else
|
||||
local width = self.get("width")
|
||||
self.set("x", value - width + 1)
|
||||
end
|
||||
elseif property == "bottom" then
|
||||
if constraints.top then
|
||||
local topValue = self:_resolveConstraint("top", constraints.top)
|
||||
local height = value - topValue + 1
|
||||
self.set("height", height)
|
||||
self.set("y", topValue)
|
||||
else
|
||||
local height = self.get("height")
|
||||
self.set("y", value - height + 1)
|
||||
end
|
||||
elseif property == "centerX" then
|
||||
local width = self.get("width")
|
||||
self.set("x", value - math.floor(width / 2))
|
||||
|
||||
@@ -58,14 +58,46 @@ function LayoutManager._applyPositions(instance)
|
||||
child.set("y", pos.y)
|
||||
child.set("width", pos.width)
|
||||
child.set("height", pos.height)
|
||||
child._layoutValues = {
|
||||
x = pos.x,
|
||||
y = pos.y,
|
||||
width = pos.width,
|
||||
height = pos.height
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Checks if a child's properties were changed by the user since last layout
|
||||
--- @param child BaseElement The child element to check
|
||||
--- @return boolean changed Whether the user changed x, y, width, or height
|
||||
--- @private
|
||||
function LayoutManager._wasChangedByUser(child)
|
||||
if not child._layoutValues then return false end
|
||||
|
||||
local currentX = child.get("x")
|
||||
local currentY = child.get("y")
|
||||
local currentWidth = child.get("width")
|
||||
local currentHeight = child.get("height")
|
||||
|
||||
return currentX ~= child._layoutValues.x or
|
||||
currentY ~= child._layoutValues.y or
|
||||
currentWidth ~= child._layoutValues.width or
|
||||
currentHeight ~= child._layoutValues.height
|
||||
end
|
||||
|
||||
--- Updates a layout instance (recalculates positions)
|
||||
--- @param instance table The layout instance
|
||||
function LayoutManager.update(instance)
|
||||
if instance and instance.layout and instance.layout.calculate then
|
||||
if instance._positions then
|
||||
for child, pos in pairs(instance._positions) do
|
||||
if not child._destroyed then
|
||||
child._userModified = LayoutManager._wasChangedByUser(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
instance.layout.calculate(instance)
|
||||
LayoutManager._applyPositions(instance)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user