spoke too soon - now fixed
This commit is contained in:
@@ -1116,7 +1116,7 @@ function Element.new(props)
|
||||
self.transition = props.transition or {}
|
||||
|
||||
-- Overflow and scroll properties
|
||||
self.overflow = props.overflow or "visible"
|
||||
self.overflow = props.overflow or "hidden"
|
||||
self.overflowX = props.overflowX
|
||||
self.overflowY = props.overflowY
|
||||
|
||||
@@ -1214,32 +1214,30 @@ function Element:_detectOverflow()
|
||||
return -- No children, no overflow
|
||||
end
|
||||
|
||||
local minX, minY = math.huge, math.huge
|
||||
local maxX, maxY = -math.huge, -math.huge
|
||||
local minX, minY = 0, 0
|
||||
local maxX, maxY = 0, 0
|
||||
|
||||
-- Content area starts after padding
|
||||
local contentX = self.x + self.padding.left
|
||||
local contentY = self.y + self.padding.top
|
||||
|
||||
for _, child in ipairs(self.children) do
|
||||
-- Skip absolutely positioned children (they don't contribute to overflow)
|
||||
if not child._explicitlyAbsolute then
|
||||
local childLeft = child.x - self.x
|
||||
local childTop = child.y - self.y
|
||||
local childRight = childLeft + child:getBorderBoxWidth() + child.margin.left + child.margin.right
|
||||
local childBottom = childTop + child:getBorderBoxHeight() + child.margin.top + child.margin.bottom
|
||||
-- Calculate child position relative to content area
|
||||
local childLeft = child.x - contentX
|
||||
local childTop = child.y - contentY
|
||||
local childRight = childLeft + child:getBorderBoxWidth() + child.margin.right
|
||||
local childBottom = childTop + child:getBorderBoxHeight() + child.margin.bottom
|
||||
|
||||
minX = math.min(minX, childLeft)
|
||||
minY = math.min(minY, childTop)
|
||||
maxX = math.max(maxX, childRight)
|
||||
maxY = math.max(maxY, childBottom)
|
||||
end
|
||||
end
|
||||
|
||||
-- If no non-absolute children, no overflow
|
||||
if minX == math.huge then
|
||||
return
|
||||
end
|
||||
|
||||
-- Calculate content dimensions
|
||||
self._contentWidth = math.max(0, maxX - minX)
|
||||
self._contentHeight = math.max(0, maxY - minY)
|
||||
self._contentWidth = maxX
|
||||
self._contentHeight = maxY
|
||||
|
||||
-- Detect overflow
|
||||
local containerWidth = self.width
|
||||
@@ -1281,7 +1279,28 @@ function Element:_calculateScrollbarDimensions()
|
||||
local overflowY = self.overflowY or self.overflow
|
||||
|
||||
-- Vertical scrollbar
|
||||
if self._overflowY and (overflowY == "scroll" or overflowY == "auto") then
|
||||
-- Note: overflow="scroll" always shows scrollbar; overflow="auto" only when content overflows
|
||||
if overflowY == "scroll" then
|
||||
-- Always show scrollbar for "scroll" mode
|
||||
result.vertical.visible = true
|
||||
result.vertical.trackHeight = self.height - (self.scrollbarPadding * 2)
|
||||
|
||||
if self._overflowY then
|
||||
-- Content overflows, calculate proper thumb size
|
||||
local contentRatio = self.height / math.max(self._contentHeight, self.height)
|
||||
result.vertical.thumbHeight = math.max(20, result.vertical.trackHeight * contentRatio)
|
||||
|
||||
-- Calculate thumb position based on scroll ratio
|
||||
local scrollRatio = self._maxScrollY > 0 and (self._scrollY / self._maxScrollY) or 0
|
||||
local maxThumbY = result.vertical.trackHeight - result.vertical.thumbHeight
|
||||
result.vertical.thumbY = maxThumbY * scrollRatio
|
||||
else
|
||||
-- No overflow, thumb fills entire track
|
||||
result.vertical.thumbHeight = result.vertical.trackHeight
|
||||
result.vertical.thumbY = 0
|
||||
end
|
||||
elseif self._overflowY and overflowY == "auto" then
|
||||
-- Only show scrollbar when content actually overflows
|
||||
result.vertical.visible = true
|
||||
result.vertical.trackHeight = self.height - (self.scrollbarPadding * 2)
|
||||
|
||||
@@ -1293,16 +1312,31 @@ function Element:_calculateScrollbarDimensions()
|
||||
local scrollRatio = self._maxScrollY > 0 and (self._scrollY / self._maxScrollY) or 0
|
||||
local maxThumbY = result.vertical.trackHeight - result.vertical.thumbHeight
|
||||
result.vertical.thumbY = maxThumbY * scrollRatio
|
||||
elseif overflowY == "scroll" then
|
||||
-- Always show scrollbar for "scroll" mode even without overflow
|
||||
result.vertical.visible = true
|
||||
result.vertical.trackHeight = self.height - (self.scrollbarPadding * 2)
|
||||
result.vertical.thumbHeight = result.vertical.trackHeight
|
||||
result.vertical.thumbY = 0
|
||||
end
|
||||
|
||||
-- Horizontal scrollbar
|
||||
if self._overflowX and (overflowX == "scroll" or overflowX == "auto") then
|
||||
-- Note: overflow="scroll" always shows scrollbar; overflow="auto" only when content overflows
|
||||
if overflowX == "scroll" then
|
||||
-- Always show scrollbar for "scroll" mode
|
||||
result.horizontal.visible = true
|
||||
result.horizontal.trackWidth = self.width - (self.scrollbarPadding * 2)
|
||||
|
||||
if self._overflowX then
|
||||
-- Content overflows, calculate proper thumb size
|
||||
local contentRatio = self.width / math.max(self._contentWidth, self.width)
|
||||
result.horizontal.thumbWidth = math.max(20, result.horizontal.trackWidth * contentRatio)
|
||||
|
||||
-- Calculate thumb position based on scroll ratio
|
||||
local scrollRatio = self._maxScrollX > 0 and (self._scrollX / self._maxScrollX) or 0
|
||||
local maxThumbX = result.horizontal.trackWidth - result.horizontal.thumbWidth
|
||||
result.horizontal.thumbX = maxThumbX * scrollRatio
|
||||
else
|
||||
-- No overflow, thumb fills entire track
|
||||
result.horizontal.thumbWidth = result.horizontal.trackWidth
|
||||
result.horizontal.thumbX = 0
|
||||
end
|
||||
elseif self._overflowX and overflowX == "auto" then
|
||||
-- Only show scrollbar when content actually overflows
|
||||
result.horizontal.visible = true
|
||||
result.horizontal.trackWidth = self.width - (self.scrollbarPadding * 2)
|
||||
|
||||
@@ -1314,12 +1348,6 @@ function Element:_calculateScrollbarDimensions()
|
||||
local scrollRatio = self._maxScrollX > 0 and (self._scrollX / self._maxScrollX) or 0
|
||||
local maxThumbX = result.horizontal.trackWidth - result.horizontal.thumbWidth
|
||||
result.horizontal.thumbX = maxThumbX * scrollRatio
|
||||
elseif overflowX == "scroll" then
|
||||
-- Always show scrollbar for "scroll" mode even without overflow
|
||||
result.horizontal.visible = true
|
||||
result.horizontal.trackWidth = self.width - (self.scrollbarPadding * 2)
|
||||
result.horizontal.thumbWidth = result.horizontal.trackWidth
|
||||
result.horizontal.thumbX = 0
|
||||
end
|
||||
|
||||
return result
|
||||
@@ -1333,8 +1361,11 @@ function Element:_drawScrollbars(dims)
|
||||
|
||||
-- Vertical scrollbar
|
||||
if dims.vertical.visible and not self.hideScrollbars.vertical then
|
||||
local trackX = x + w - self.scrollbarWidth - self.scrollbarPadding + self.padding.left
|
||||
local trackY = y + self.scrollbarPadding + self.padding.top
|
||||
-- Position scrollbar within content area (x, y is border-box origin)
|
||||
local contentX = x + self.padding.left
|
||||
local contentY = y + self.padding.top
|
||||
local trackX = contentX + w - self.scrollbarWidth - self.scrollbarPadding
|
||||
local trackY = contentY + self.scrollbarPadding
|
||||
|
||||
-- Determine thumb color based on state (independent for vertical)
|
||||
local thumbColor = self.scrollbarColor
|
||||
@@ -1357,8 +1388,11 @@ function Element:_drawScrollbars(dims)
|
||||
|
||||
-- Horizontal scrollbar
|
||||
if dims.horizontal.visible and not self.hideScrollbars.horizontal then
|
||||
local trackX = x + self.scrollbarPadding + self.padding.left
|
||||
local trackY = y + h - self.scrollbarWidth - self.scrollbarPadding + self.padding.top
|
||||
-- Position scrollbar within content area (x, y is border-box origin)
|
||||
local contentX = x + self.padding.left
|
||||
local contentY = y + self.padding.top
|
||||
local trackX = contentX + self.scrollbarPadding
|
||||
local trackY = contentY + h - self.scrollbarWidth - self.scrollbarPadding
|
||||
|
||||
-- Determine thumb color based on state (independent for horizontal)
|
||||
local thumbColor = self.scrollbarColor
|
||||
@@ -1401,8 +1435,11 @@ function Element:_getScrollbarAtPosition(mouseX, mouseY)
|
||||
|
||||
-- Check vertical scrollbar (only if not hidden)
|
||||
if dims.vertical.visible and not self.hideScrollbars.vertical then
|
||||
local trackX = x + w - self.scrollbarWidth - self.scrollbarPadding + self.padding.left
|
||||
local trackY = y + self.scrollbarPadding + self.padding.top
|
||||
-- Position scrollbar within content area (x, y is border-box origin)
|
||||
local contentX = x + self.padding.left
|
||||
local contentY = y + self.padding.top
|
||||
local trackX = contentX + w - self.scrollbarWidth - self.scrollbarPadding
|
||||
local trackY = contentY + self.scrollbarPadding
|
||||
local trackW = self.scrollbarWidth
|
||||
local trackH = dims.vertical.trackHeight
|
||||
|
||||
@@ -1420,8 +1457,11 @@ function Element:_getScrollbarAtPosition(mouseX, mouseY)
|
||||
|
||||
-- Check horizontal scrollbar (only if not hidden)
|
||||
if dims.horizontal.visible and not self.hideScrollbars.horizontal then
|
||||
local trackX = x + self.scrollbarPadding + self.padding.left
|
||||
local trackY = y + h - self.scrollbarWidth - self.scrollbarPadding + self.padding.top
|
||||
-- Position scrollbar within content area (x, y is border-box origin)
|
||||
local contentX = x + self.padding.left
|
||||
local contentY = y + self.padding.top
|
||||
local trackX = contentX + self.scrollbarPadding
|
||||
local trackY = contentY + h - self.scrollbarWidth - self.scrollbarPadding
|
||||
local trackW = dims.horizontal.trackWidth
|
||||
local trackH = self.scrollbarWidth
|
||||
|
||||
@@ -1462,11 +1502,13 @@ function Element:_handleScrollbarPress(mouseX, mouseY, button)
|
||||
local dims = self:_calculateScrollbarDimensions()
|
||||
|
||||
if scrollbar.component == "vertical" then
|
||||
local trackY = self.y + self.scrollbarPadding + self.padding.top
|
||||
local contentY = self.y + self.padding.top
|
||||
local trackY = contentY + self.scrollbarPadding
|
||||
local thumbY = trackY + dims.vertical.thumbY
|
||||
self._scrollbarDragOffset = mouseY - thumbY
|
||||
elseif scrollbar.component == "horizontal" then
|
||||
local trackX = self.x + self.scrollbarPadding + self.padding.left
|
||||
local contentX = self.x + self.padding.left
|
||||
local trackX = contentX + self.scrollbarPadding
|
||||
local thumbX = trackX + dims.horizontal.thumbX
|
||||
self._scrollbarDragOffset = mouseX - thumbX
|
||||
end
|
||||
@@ -1493,7 +1535,8 @@ function Element:_handleScrollbarDrag(mouseX, mouseY)
|
||||
local dims = self:_calculateScrollbarDimensions()
|
||||
|
||||
if self._hoveredScrollbar == "vertical" then
|
||||
local trackY = self.y + self.scrollbarPadding + self.padding.top
|
||||
local contentY = self.y + self.padding.top
|
||||
local trackY = contentY + self.scrollbarPadding
|
||||
local trackH = dims.vertical.trackHeight
|
||||
local thumbH = dims.vertical.thumbHeight
|
||||
|
||||
@@ -1508,7 +1551,8 @@ function Element:_handleScrollbarDrag(mouseX, mouseY)
|
||||
self:setScrollPosition(nil, newScrollY)
|
||||
return true
|
||||
elseif self._hoveredScrollbar == "horizontal" then
|
||||
local trackX = self.x + self.scrollbarPadding + self.padding.left
|
||||
local contentX = self.x + self.padding.left
|
||||
local trackX = contentX + self.scrollbarPadding
|
||||
local trackW = dims.horizontal.trackWidth
|
||||
local thumbW = dims.horizontal.thumbWidth
|
||||
|
||||
@@ -1551,7 +1595,8 @@ function Element:_scrollToTrackPosition(mouseX, mouseY, component)
|
||||
local dims = self:_calculateScrollbarDimensions()
|
||||
|
||||
if component == "vertical" then
|
||||
local trackY = self.y + self.scrollbarPadding + self.padding.top
|
||||
local contentY = self.y + self.padding.top
|
||||
local trackY = contentY + self.scrollbarPadding
|
||||
local trackH = dims.vertical.trackHeight
|
||||
local thumbH = dims.vertical.thumbHeight
|
||||
|
||||
@@ -1565,7 +1610,8 @@ function Element:_scrollToTrackPosition(mouseX, mouseY, component)
|
||||
|
||||
self:setScrollPosition(nil, newScrollY)
|
||||
elseif component == "horizontal" then
|
||||
local trackX = self.x + self.scrollbarPadding + self.padding.left
|
||||
local contentX = self.x + self.padding.left
|
||||
local trackX = contentX + self.scrollbarPadding
|
||||
local trackW = dims.horizontal.trackWidth
|
||||
local thumbW = dims.horizontal.thumbWidth
|
||||
|
||||
@@ -2614,7 +2660,8 @@ function Element:draw(backdropCanvas)
|
||||
|
||||
-- Helper function to draw children (with or without clipping)
|
||||
local function drawChildren()
|
||||
-- Determine if we need overflow clipping
|
||||
-- Determine overflow behavior per axis (matches HTML/CSS behavior)
|
||||
-- Priority: axis-specific (overflowX/Y) > general (overflow) > default (hidden)
|
||||
local overflowX = self.overflowX or self.overflow
|
||||
local overflowY = self.overflowY or self.overflow
|
||||
local needsOverflowClipping = (overflowX ~= "visible" or overflowY ~= "visible") and (overflowX ~= nil or overflowY ~= nil)
|
||||
@@ -2622,11 +2669,6 @@ function Element:draw(backdropCanvas)
|
||||
-- Apply scroll offset if overflow is not visible
|
||||
local hasScrollOffset = needsOverflowClipping and (self._scrollX ~= 0 or self._scrollY ~= 0)
|
||||
|
||||
if hasScrollOffset then
|
||||
love.graphics.push()
|
||||
love.graphics.translate(-self._scrollX, -self._scrollY)
|
||||
end
|
||||
|
||||
if hasRoundedCorners and #sortedChildren > 0 then
|
||||
-- Use stencil to clip children to rounded rectangle
|
||||
-- BORDER-BOX MODEL: Use stored border-box dimensions for clipping
|
||||
@@ -2637,10 +2679,20 @@ function Element:draw(backdropCanvas)
|
||||
love.graphics.stencil(stencilFunc, "replace", 1)
|
||||
love.graphics.setStencilTest("greater", 0)
|
||||
|
||||
-- Apply scroll offset AFTER clipping is set
|
||||
if hasScrollOffset then
|
||||
love.graphics.push()
|
||||
love.graphics.translate(-self._scrollX, -self._scrollY)
|
||||
end
|
||||
|
||||
for _, child in ipairs(sortedChildren) do
|
||||
child:draw(backdropCanvas)
|
||||
end
|
||||
|
||||
if hasScrollOffset then
|
||||
love.graphics.pop()
|
||||
end
|
||||
|
||||
love.graphics.setStencilTest()
|
||||
elseif needsOverflowClipping and #sortedChildren > 0 then
|
||||
-- Clip content for overflow hidden/scroll/auto without rounded corners
|
||||
@@ -2651,10 +2703,20 @@ function Element:draw(backdropCanvas)
|
||||
|
||||
love.graphics.setScissor(contentX, contentY, contentWidth, contentHeight)
|
||||
|
||||
-- Apply scroll offset AFTER clipping is set
|
||||
if hasScrollOffset then
|
||||
love.graphics.push()
|
||||
love.graphics.translate(-self._scrollX, -self._scrollY)
|
||||
end
|
||||
|
||||
for _, child in ipairs(sortedChildren) do
|
||||
child:draw(backdropCanvas)
|
||||
end
|
||||
|
||||
if hasScrollOffset then
|
||||
love.graphics.pop()
|
||||
end
|
||||
|
||||
love.graphics.setScissor()
|
||||
else
|
||||
-- No clipping needed
|
||||
@@ -2662,10 +2724,6 @@ function Element:draw(backdropCanvas)
|
||||
child:draw(backdropCanvas)
|
||||
end
|
||||
end
|
||||
|
||||
if hasScrollOffset then
|
||||
love.graphics.pop()
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply content blur if configured
|
||||
@@ -2681,11 +2739,14 @@ function Element:draw(backdropCanvas)
|
||||
end
|
||||
|
||||
-- Draw scrollbars if overflow is scroll or auto
|
||||
-- IMPORTANT: Scrollbars must be drawn without parent clipping
|
||||
local overflowX = self.overflowX or self.overflow
|
||||
local overflowY = self.overflowY or self.overflow
|
||||
if overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto" then
|
||||
local scrollbarDims = self:_calculateScrollbarDimensions()
|
||||
if scrollbarDims.vertical.visible or scrollbarDims.horizontal.visible then
|
||||
-- Clear any parent scissor clipping before drawing scrollbars
|
||||
love.graphics.setScissor()
|
||||
self:_drawScrollbars(scrollbarDims)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user