checking logic
This commit is contained in:
369
FlexLove.lua
369
FlexLove.lua
@@ -814,7 +814,7 @@ function ImageCache.load(imagePath, loadImageData)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local normalizedPath = normalizePath(imagePath)
|
local normalizedPath = normalizePath(imagePath)
|
||||||
|
|
||||||
-- Check if already cached
|
-- Check if already cached
|
||||||
if ImageCache._cache[normalizedPath] then
|
if ImageCache._cache[normalizedPath] then
|
||||||
return ImageCache._cache[normalizedPath].image, nil
|
return ImageCache._cache[normalizedPath].image, nil
|
||||||
@@ -840,7 +840,7 @@ function ImageCache.load(imagePath, loadImageData)
|
|||||||
-- Cache the image
|
-- Cache the image
|
||||||
ImageCache._cache[normalizedPath] = {
|
ImageCache._cache[normalizedPath] = {
|
||||||
image = image,
|
image = image,
|
||||||
imageData = imgData
|
imageData = imgData,
|
||||||
}
|
}
|
||||||
|
|
||||||
return image, nil
|
return image, nil
|
||||||
@@ -927,7 +927,7 @@ function ImageCache.getStats()
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
count = count,
|
count = count,
|
||||||
memoryEstimate = memoryEstimate
|
memoryEstimate = memoryEstimate,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -957,16 +957,16 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
|
|||||||
end
|
end
|
||||||
|
|
||||||
local result = {
|
local result = {
|
||||||
sx = 0, -- Source X
|
sx = 0, -- Source X
|
||||||
sy = 0, -- Source Y
|
sy = 0, -- Source Y
|
||||||
sw = imageWidth, -- Source width
|
sw = imageWidth, -- Source width
|
||||||
sh = imageHeight, -- Source height
|
sh = imageHeight, -- Source height
|
||||||
dx = 0, -- Destination X
|
dx = 0, -- Destination X
|
||||||
dy = 0, -- Destination Y
|
dy = 0, -- Destination Y
|
||||||
dw = boundsWidth, -- Destination width
|
dw = boundsWidth, -- Destination width
|
||||||
dh = boundsHeight, -- Destination height
|
dh = boundsHeight, -- Destination height
|
||||||
scaleX = 1, -- Scale factor X
|
scaleX = 1, -- Scale factor X
|
||||||
scaleY = 1 -- Scale factor Y
|
scaleY = 1, -- Scale factor Y
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Calculate based on fit mode
|
-- Calculate based on fit mode
|
||||||
@@ -976,7 +976,6 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
|
|||||||
result.scaleY = boundsHeight / imageHeight
|
result.scaleY = boundsHeight / imageHeight
|
||||||
result.dw = boundsWidth
|
result.dw = boundsWidth
|
||||||
result.dh = boundsHeight
|
result.dh = boundsHeight
|
||||||
|
|
||||||
elseif fitMode == "contain" then
|
elseif fitMode == "contain" then
|
||||||
-- Scale to fit within bounds (preserves aspect ratio)
|
-- Scale to fit within bounds (preserves aspect ratio)
|
||||||
local scale = math.min(boundsWidth / imageWidth, boundsHeight / imageHeight)
|
local scale = math.min(boundsWidth / imageWidth, boundsHeight / imageHeight)
|
||||||
@@ -989,19 +988,18 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
|
|||||||
local posX, posY = ImageRenderer._parsePosition(objectPosition)
|
local posX, posY = ImageRenderer._parsePosition(objectPosition)
|
||||||
result.dx = (boundsWidth - result.dw) * posX
|
result.dx = (boundsWidth - result.dw) * posX
|
||||||
result.dy = (boundsHeight - result.dh) * posY
|
result.dy = (boundsHeight - result.dh) * posY
|
||||||
|
|
||||||
elseif fitMode == "cover" then
|
elseif fitMode == "cover" then
|
||||||
-- Scale to cover bounds (preserves aspect ratio, may crop)
|
-- Scale to cover bounds (preserves aspect ratio, may crop)
|
||||||
local scale = math.max(boundsWidth / imageWidth, boundsHeight / imageHeight)
|
local scale = math.max(boundsWidth / imageWidth, boundsHeight / imageHeight)
|
||||||
result.scaleX = scale
|
result.scaleX = scale
|
||||||
result.scaleY = scale
|
result.scaleY = scale
|
||||||
|
|
||||||
local scaledWidth = imageWidth * scale
|
local scaledWidth = imageWidth * scale
|
||||||
local scaledHeight = imageHeight * scale
|
local scaledHeight = imageHeight * scale
|
||||||
|
|
||||||
-- Apply object-position for crop alignment
|
-- Apply object-position for crop alignment
|
||||||
local posX, posY = ImageRenderer._parsePosition(objectPosition)
|
local posX, posY = ImageRenderer._parsePosition(objectPosition)
|
||||||
|
|
||||||
-- Calculate which part of the scaled image to show
|
-- Calculate which part of the scaled image to show
|
||||||
local cropX = (scaledWidth - boundsWidth) * posX
|
local cropX = (scaledWidth - boundsWidth) * posX
|
||||||
local cropY = (scaledHeight - boundsHeight) * posY
|
local cropY = (scaledHeight - boundsHeight) * posY
|
||||||
@@ -1011,12 +1009,11 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
|
|||||||
result.sy = cropY / scale
|
result.sy = cropY / scale
|
||||||
result.sw = boundsWidth / scale
|
result.sw = boundsWidth / scale
|
||||||
result.sh = boundsHeight / scale
|
result.sh = boundsHeight / scale
|
||||||
|
|
||||||
result.dx = 0
|
result.dx = 0
|
||||||
result.dy = 0
|
result.dy = 0
|
||||||
result.dw = boundsWidth
|
result.dw = boundsWidth
|
||||||
result.dh = boundsHeight
|
result.dh = boundsHeight
|
||||||
|
|
||||||
elseif fitMode == "none" then
|
elseif fitMode == "none" then
|
||||||
-- Use natural size (no scaling)
|
-- Use natural size (no scaling)
|
||||||
result.scaleX = 1
|
result.scaleX = 1
|
||||||
@@ -1028,7 +1025,6 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
|
|||||||
local posX, posY = ImageRenderer._parsePosition(objectPosition)
|
local posX, posY = ImageRenderer._parsePosition(objectPosition)
|
||||||
result.dx = (boundsWidth - imageWidth) * posX
|
result.dx = (boundsWidth - imageWidth) * posX
|
||||||
result.dy = (boundsHeight - imageHeight) * posY
|
result.dy = (boundsHeight - imageHeight) * posY
|
||||||
|
|
||||||
elseif fitMode == "scale-down" then
|
elseif fitMode == "scale-down" then
|
||||||
-- Use none or contain, whichever is smaller
|
-- Use none or contain, whichever is smaller
|
||||||
if imageWidth <= boundsWidth and imageHeight <= boundsHeight then
|
if imageWidth <= boundsWidth and imageHeight <= boundsHeight then
|
||||||
@@ -1038,7 +1034,6 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
|
|||||||
-- Image too large, use "contain"
|
-- Image too large, use "contain"
|
||||||
return ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, boundsHeight, "contain", objectPosition)
|
return ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, boundsHeight, "contain", objectPosition)
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
error(formatError("ImageRenderer", string.format("Invalid fit mode: '%s'. Must be one of: fill, contain, cover, scale-down, none", tostring(fitMode))))
|
error(formatError("ImageRenderer", string.format("Invalid fit mode: '%s'. Must be one of: fill, contain, cover, scale-down, none", tostring(fitMode))))
|
||||||
end
|
end
|
||||||
@@ -1065,11 +1060,11 @@ function ImageRenderer._parsePosition(position)
|
|||||||
if #parts == 1 then
|
if #parts == 1 then
|
||||||
local val = parts[1]
|
local val = parts[1]
|
||||||
if val == "left" or val == "right" then
|
if val == "left" or val == "right" then
|
||||||
parts = {val, "center"}
|
parts = { val, "center" }
|
||||||
elseif val == "top" or val == "bottom" then
|
elseif val == "top" or val == "bottom" then
|
||||||
parts = {"center", val}
|
parts = { "center", val }
|
||||||
else
|
else
|
||||||
parts = {val, val}
|
parts = { val, val }
|
||||||
end
|
end
|
||||||
elseif #parts == 0 then
|
elseif #parts == 0 then
|
||||||
return 0.5, 0.5 -- Default to center
|
return 0.5, 0.5 -- Default to center
|
||||||
@@ -1077,9 +1072,12 @@ function ImageRenderer._parsePosition(position)
|
|||||||
|
|
||||||
local function parseValue(val)
|
local function parseValue(val)
|
||||||
-- Handle keywords
|
-- Handle keywords
|
||||||
if val == "center" then return 0.5
|
if val == "center" then
|
||||||
elseif val == "left" or val == "top" then return 0
|
return 0.5
|
||||||
elseif val == "right" or val == "bottom" then return 1
|
elseif val == "left" or val == "top" then
|
||||||
|
return 0
|
||||||
|
elseif val == "right" or val == "bottom" then
|
||||||
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle percentages
|
-- Handle percentages
|
||||||
@@ -2340,6 +2338,29 @@ function Gui.init(config)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Check for Z-index coverage (occlusion)
|
||||||
|
---@param elem Element
|
||||||
|
---@param clickX number
|
||||||
|
---@param clickY number
|
||||||
|
---@return boolean
|
||||||
|
function Gui.isOccluded(elem, clickX, clickY)
|
||||||
|
for _, element in ipairs(Gui.topElements) do
|
||||||
|
if element.z > elem.z and element:contains(clickX, clickY) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
--TODO: check if walking the children tree is necessary here - might only need to check for absolute positioned
|
||||||
|
--children
|
||||||
|
for _, child in ipairs(element.children) do
|
||||||
|
if child.positioning == "absolute" then
|
||||||
|
if child.z > elem.z and child:contains(clickX, clickY) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
--- Get current scale factors
|
--- Get current scale factors
|
||||||
---@return number, number -- scaleX, scaleY
|
---@return number, number -- scaleX, scaleY
|
||||||
function Gui.getScaleFactors()
|
function Gui.getScaleFactors()
|
||||||
@@ -2398,7 +2419,7 @@ Gui._canvasDimensions = { width = 0, height = 0 }
|
|||||||
function Gui.draw(gameDrawFunc, postDrawFunc)
|
function Gui.draw(gameDrawFunc, postDrawFunc)
|
||||||
-- Save the current canvas state to support nested rendering
|
-- Save the current canvas state to support nested rendering
|
||||||
local outerCanvas = love.graphics.getCanvas()
|
local outerCanvas = love.graphics.getCanvas()
|
||||||
|
|
||||||
local gameCanvas = nil
|
local gameCanvas = nil
|
||||||
|
|
||||||
-- Render game content to a canvas if function provided
|
-- Render game content to a canvas if function provided
|
||||||
@@ -2414,7 +2435,7 @@ function Gui.draw(gameDrawFunc, postDrawFunc)
|
|||||||
end
|
end
|
||||||
|
|
||||||
gameCanvas = Gui._gameCanvas
|
gameCanvas = Gui._gameCanvas
|
||||||
|
|
||||||
love.graphics.setCanvas(gameCanvas)
|
love.graphics.setCanvas(gameCanvas)
|
||||||
love.graphics.clear()
|
love.graphics.clear()
|
||||||
gameDrawFunc() -- Call the drawing function
|
gameDrawFunc() -- Call the drawing function
|
||||||
@@ -2574,39 +2595,40 @@ end
|
|||||||
function Gui.wheelmoved(x, y)
|
function Gui.wheelmoved(x, y)
|
||||||
-- Get mouse position
|
-- Get mouse position
|
||||||
local mx, my = love.mouse.getPosition()
|
local mx, my = love.mouse.getPosition()
|
||||||
|
|
||||||
-- Find the deepest scrollable element at mouse position
|
-- Find the deepest scrollable element at mouse position
|
||||||
local function findScrollableAtPosition(elements, mx, my)
|
local function findScrollableAtPosition(elements, mx, my)
|
||||||
-- Check in reverse z-order (top to bottom)
|
-- Check in reverse z-order (top to bottom)
|
||||||
for i = #elements, 1, -1 do
|
for i = #elements, 1, -1 do
|
||||||
local element = elements[i]
|
local element = elements[i]
|
||||||
|
|
||||||
-- Check if mouse is over element
|
-- Check if mouse is over element
|
||||||
local bx = element.x
|
local bx = element.x
|
||||||
local by = element.y
|
local by = element.y
|
||||||
local bw = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right)
|
local bw = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right)
|
||||||
local bh = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom)
|
local bh = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom)
|
||||||
|
|
||||||
if mx >= bx and mx <= bx + bw and my >= by and my <= by + bh then
|
if mx >= bx and mx <= bx + bw and my >= by and my <= by + bh then
|
||||||
-- Check children first (depth-first)
|
-- Check children first (depth-first)
|
||||||
if #element.children > 0 then
|
if #element.children > 0 then
|
||||||
local childResult = findScrollableAtPosition(element.children, mx, my)
|
local childResult = findScrollableAtPosition(element.children, mx, my)
|
||||||
if childResult then return childResult end
|
if childResult then
|
||||||
|
return childResult
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if this element is scrollable
|
-- Check if this element is scrollable
|
||||||
local overflowX = element.overflowX or element.overflow
|
local overflowX = element.overflowX or element.overflow
|
||||||
local overflowY = element.overflowY or element.overflow
|
local overflowY = element.overflowY or element.overflow
|
||||||
if (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") and
|
if (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") and (element._overflowX or element._overflowY) then
|
||||||
(element._overflowX or element._overflowY) then
|
|
||||||
return element
|
return element
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local scrollableElement = findScrollableAtPosition(Gui.topElements, mx, my)
|
local scrollableElement = findScrollableAtPosition(Gui.topElements, mx, my)
|
||||||
if scrollableElement then
|
if scrollableElement then
|
||||||
scrollableElement:_handleWheelScroll(x, y)
|
scrollableElement:_handleWheelScroll(x, y)
|
||||||
@@ -3195,7 +3217,7 @@ function Element.new(props)
|
|||||||
self._lastClickButton = nil
|
self._lastClickButton = nil
|
||||||
self._clickCount = 0
|
self._clickCount = 0
|
||||||
self._touchPressed = {}
|
self._touchPressed = {}
|
||||||
|
|
||||||
-- Initialize drag tracking for event system
|
-- Initialize drag tracking for event system
|
||||||
self._dragStartX = {} -- Track drag start X position per mouse button
|
self._dragStartX = {} -- Track drag start X position per mouse button
|
||||||
self._dragStartY = {} -- Track drag start Y position per mouse button
|
self._dragStartY = {} -- Track drag start Y position per mouse button
|
||||||
@@ -3378,7 +3400,7 @@ function Element.new(props)
|
|||||||
self.objectFit = props.objectFit or "fill"
|
self.objectFit = props.objectFit or "fill"
|
||||||
self.objectPosition = props.objectPosition or "center center"
|
self.objectPosition = props.objectPosition or "center center"
|
||||||
self.imageOpacity = props.imageOpacity or 1
|
self.imageOpacity = props.imageOpacity or 1
|
||||||
|
|
||||||
-- Auto-load image if imagePath is provided
|
-- Auto-load image if imagePath is provided
|
||||||
if self.imagePath and not self.image then
|
if self.imagePath and not self.image then
|
||||||
local loadedImage, err = ImageCache.load(self.imagePath)
|
local loadedImage, err = ImageCache.load(self.imagePath)
|
||||||
@@ -4118,7 +4140,7 @@ function Element.new(props)
|
|||||||
self.overflow = props.overflow or "visible"
|
self.overflow = props.overflow or "visible"
|
||||||
self.overflowX = props.overflowX
|
self.overflowX = props.overflowX
|
||||||
self.overflowY = props.overflowY
|
self.overflowY = props.overflowY
|
||||||
|
|
||||||
-- Scrollbar configuration
|
-- Scrollbar configuration
|
||||||
self.scrollbarWidth = props.scrollbarWidth or 12
|
self.scrollbarWidth = props.scrollbarWidth or 12
|
||||||
self.scrollbarColor = props.scrollbarColor or Color.new(0.5, 0.5, 0.5, 0.8)
|
self.scrollbarColor = props.scrollbarColor or Color.new(0.5, 0.5, 0.5, 0.8)
|
||||||
@@ -4126,24 +4148,24 @@ function Element.new(props)
|
|||||||
self.scrollbarRadius = props.scrollbarRadius or 6
|
self.scrollbarRadius = props.scrollbarRadius or 6
|
||||||
self.scrollbarPadding = props.scrollbarPadding or 2
|
self.scrollbarPadding = props.scrollbarPadding or 2
|
||||||
self.scrollSpeed = props.scrollSpeed or 20
|
self.scrollSpeed = props.scrollSpeed or 20
|
||||||
|
|
||||||
-- Internal overflow state
|
-- Internal overflow state
|
||||||
self._overflowX = false
|
self._overflowX = false
|
||||||
self._overflowY = false
|
self._overflowY = false
|
||||||
self._contentWidth = 0
|
self._contentWidth = 0
|
||||||
self._contentHeight = 0
|
self._contentHeight = 0
|
||||||
|
|
||||||
-- Scroll state
|
-- Scroll state
|
||||||
self._scrollX = 0
|
self._scrollX = 0
|
||||||
self._scrollY = 0
|
self._scrollY = 0
|
||||||
self._maxScrollX = 0
|
self._maxScrollX = 0
|
||||||
self._maxScrollY = 0
|
self._maxScrollY = 0
|
||||||
|
|
||||||
-- Scrollbar interaction state
|
-- Scrollbar interaction state
|
||||||
self._scrollbarHovered = false
|
self._scrollbarHovered = false
|
||||||
self._scrollbarDragging = false
|
self._scrollbarDragging = false
|
||||||
self._hoveredScrollbar = nil -- "vertical" or "horizontal"
|
self._hoveredScrollbar = nil -- "vertical" or "horizontal"
|
||||||
self._scrollbarDragOffset = 0 -- Offset from thumb top when drag started
|
self._scrollbarDragOffset = 0 -- Offset from thumb top when drag started
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -4154,6 +4176,15 @@ function Element:getBounds()
|
|||||||
return { x = self.x, y = self.y, width = self:getBorderBoxWidth(), height = self:getBorderBoxHeight() }
|
return { x = self.x, y = self.y, width = self:getBorderBoxWidth(), height = self:getBorderBoxHeight() }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Check if point is inside element bounds
|
||||||
|
--- @param x number
|
||||||
|
--- @param y number
|
||||||
|
--- @return boolean
|
||||||
|
function Element:contains(x, y)
|
||||||
|
local bounds = self:getBounds()
|
||||||
|
return bounds.x <= x and bounds.y <= y and bounds.x + bounds.width >= x and bounds.y + bounds.height >= y
|
||||||
|
end
|
||||||
|
|
||||||
--- Get border-box width (including padding)
|
--- Get border-box width (including padding)
|
||||||
---@return number
|
---@return number
|
||||||
function Element:getBorderBoxWidth()
|
function Element:getBorderBoxWidth()
|
||||||
@@ -4173,22 +4204,22 @@ function Element:_detectOverflow()
|
|||||||
self._overflowY = false
|
self._overflowY = false
|
||||||
self._contentWidth = self.width
|
self._contentWidth = self.width
|
||||||
self._contentHeight = self.height
|
self._contentHeight = self.height
|
||||||
|
|
||||||
-- Skip detection if overflow is visible (no clipping needed)
|
-- Skip detection if overflow is visible (no clipping needed)
|
||||||
local overflowX = self.overflowX or self.overflow
|
local overflowX = self.overflowX or self.overflow
|
||||||
local overflowY = self.overflowY or self.overflow
|
local overflowY = self.overflowY or self.overflow
|
||||||
if overflowX == "visible" and overflowY == "visible" then
|
if overflowX == "visible" and overflowY == "visible" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Calculate content bounds based on children
|
-- Calculate content bounds based on children
|
||||||
if #self.children == 0 then
|
if #self.children == 0 then
|
||||||
return -- No children, no overflow
|
return -- No children, no overflow
|
||||||
end
|
end
|
||||||
|
|
||||||
local minX, minY = math.huge, math.huge
|
local minX, minY = math.huge, math.huge
|
||||||
local maxX, maxY = -math.huge, -math.huge
|
local maxX, maxY = -math.huge, -math.huge
|
||||||
|
|
||||||
for _, child in ipairs(self.children) do
|
for _, child in ipairs(self.children) do
|
||||||
-- Skip absolutely positioned children (they don't contribute to overflow)
|
-- Skip absolutely positioned children (they don't contribute to overflow)
|
||||||
if not child._explicitlyAbsolute then
|
if not child._explicitlyAbsolute then
|
||||||
@@ -4196,34 +4227,34 @@ function Element:_detectOverflow()
|
|||||||
local childTop = child.y - self.y
|
local childTop = child.y - self.y
|
||||||
local childRight = childLeft + child:getBorderBoxWidth() + child.margin.left + child.margin.right
|
local childRight = childLeft + child:getBorderBoxWidth() + child.margin.left + child.margin.right
|
||||||
local childBottom = childTop + child:getBorderBoxHeight() + child.margin.top + child.margin.bottom
|
local childBottom = childTop + child:getBorderBoxHeight() + child.margin.top + child.margin.bottom
|
||||||
|
|
||||||
minX = math.min(minX, childLeft)
|
minX = math.min(minX, childLeft)
|
||||||
minY = math.min(minY, childTop)
|
minY = math.min(minY, childTop)
|
||||||
maxX = math.max(maxX, childRight)
|
maxX = math.max(maxX, childRight)
|
||||||
maxY = math.max(maxY, childBottom)
|
maxY = math.max(maxY, childBottom)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If no non-absolute children, no overflow
|
-- If no non-absolute children, no overflow
|
||||||
if minX == math.huge then
|
if minX == math.huge then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Calculate content dimensions
|
-- Calculate content dimensions
|
||||||
self._contentWidth = math.max(0, maxX - minX)
|
self._contentWidth = math.max(0, maxX - minX)
|
||||||
self._contentHeight = math.max(0, maxY - minY)
|
self._contentHeight = math.max(0, maxY - minY)
|
||||||
|
|
||||||
-- Detect overflow
|
-- Detect overflow
|
||||||
local containerWidth = self.width
|
local containerWidth = self.width
|
||||||
local containerHeight = self.height
|
local containerHeight = self.height
|
||||||
|
|
||||||
self._overflowX = self._contentWidth > containerWidth
|
self._overflowX = self._contentWidth > containerWidth
|
||||||
self._overflowY = self._contentHeight > containerHeight
|
self._overflowY = self._contentHeight > containerHeight
|
||||||
|
|
||||||
-- Calculate maximum scroll bounds
|
-- Calculate maximum scroll bounds
|
||||||
self._maxScrollX = math.max(0, self._contentWidth - containerWidth)
|
self._maxScrollX = math.max(0, self._contentWidth - containerWidth)
|
||||||
self._maxScrollY = math.max(0, self._contentHeight - containerHeight)
|
self._maxScrollY = math.max(0, self._contentHeight - containerHeight)
|
||||||
|
|
||||||
-- Clamp current scroll position to new bounds
|
-- Clamp current scroll position to new bounds
|
||||||
self._scrollX = math.max(0, math.min(self._scrollX, self._maxScrollX))
|
self._scrollX = math.max(0, math.min(self._scrollX, self._maxScrollX))
|
||||||
self._scrollY = math.max(0, math.min(self._scrollY, self._maxScrollY))
|
self._scrollY = math.max(0, math.min(self._scrollY, self._maxScrollY))
|
||||||
@@ -4246,21 +4277,21 @@ end
|
|||||||
function Element:_calculateScrollbarDimensions()
|
function Element:_calculateScrollbarDimensions()
|
||||||
local result = {
|
local result = {
|
||||||
vertical = { visible = false, trackHeight = 0, thumbHeight = 0, thumbY = 0 },
|
vertical = { visible = false, trackHeight = 0, thumbHeight = 0, thumbY = 0 },
|
||||||
horizontal = { visible = false, trackWidth = 0, thumbWidth = 0, thumbX = 0 }
|
horizontal = { visible = false, trackWidth = 0, thumbWidth = 0, thumbX = 0 },
|
||||||
}
|
}
|
||||||
|
|
||||||
local overflowX = self.overflowX or self.overflow
|
local overflowX = self.overflowX or self.overflow
|
||||||
local overflowY = self.overflowY or self.overflow
|
local overflowY = self.overflowY or self.overflow
|
||||||
|
|
||||||
-- Vertical scrollbar
|
-- Vertical scrollbar
|
||||||
if self._overflowY and (overflowY == "scroll" or overflowY == "auto") then
|
if self._overflowY and (overflowY == "scroll" or overflowY == "auto") then
|
||||||
result.vertical.visible = true
|
result.vertical.visible = true
|
||||||
result.vertical.trackHeight = self.height - (self.scrollbarPadding * 2)
|
result.vertical.trackHeight = self.height - (self.scrollbarPadding * 2)
|
||||||
|
|
||||||
-- Calculate thumb height based on content ratio
|
-- Calculate thumb height based on content ratio
|
||||||
local contentRatio = self.height / math.max(self._contentHeight, self.height)
|
local contentRatio = self.height / math.max(self._contentHeight, self.height)
|
||||||
result.vertical.thumbHeight = math.max(20, result.vertical.trackHeight * contentRatio)
|
result.vertical.thumbHeight = math.max(20, result.vertical.trackHeight * contentRatio)
|
||||||
|
|
||||||
-- Calculate thumb position based on scroll ratio
|
-- Calculate thumb position based on scroll ratio
|
||||||
local scrollRatio = self._maxScrollY > 0 and (self._scrollY / self._maxScrollY) or 0
|
local scrollRatio = self._maxScrollY > 0 and (self._scrollY / self._maxScrollY) or 0
|
||||||
local maxThumbY = result.vertical.trackHeight - result.vertical.thumbHeight
|
local maxThumbY = result.vertical.trackHeight - result.vertical.thumbHeight
|
||||||
@@ -4272,16 +4303,16 @@ function Element:_calculateScrollbarDimensions()
|
|||||||
result.vertical.thumbHeight = result.vertical.trackHeight
|
result.vertical.thumbHeight = result.vertical.trackHeight
|
||||||
result.vertical.thumbY = 0
|
result.vertical.thumbY = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Horizontal scrollbar
|
-- Horizontal scrollbar
|
||||||
if self._overflowX and (overflowX == "scroll" or overflowX == "auto") then
|
if self._overflowX and (overflowX == "scroll" or overflowX == "auto") then
|
||||||
result.horizontal.visible = true
|
result.horizontal.visible = true
|
||||||
result.horizontal.trackWidth = self.width - (self.scrollbarPadding * 2)
|
result.horizontal.trackWidth = self.width - (self.scrollbarPadding * 2)
|
||||||
|
|
||||||
-- Calculate thumb width based on content ratio
|
-- Calculate thumb width based on content ratio
|
||||||
local contentRatio = self.width / math.max(self._contentWidth, self.width)
|
local contentRatio = self.width / math.max(self._contentWidth, self.width)
|
||||||
result.horizontal.thumbWidth = math.max(20, result.horizontal.trackWidth * contentRatio)
|
result.horizontal.thumbWidth = math.max(20, result.horizontal.trackWidth * contentRatio)
|
||||||
|
|
||||||
-- Calculate thumb position based on scroll ratio
|
-- Calculate thumb position based on scroll ratio
|
||||||
local scrollRatio = self._maxScrollX > 0 and (self._scrollX / self._maxScrollX) or 0
|
local scrollRatio = self._maxScrollX > 0 and (self._scrollX / self._maxScrollX) or 0
|
||||||
local maxThumbX = result.horizontal.trackWidth - result.horizontal.thumbWidth
|
local maxThumbX = result.horizontal.trackWidth - result.horizontal.thumbWidth
|
||||||
@@ -4293,7 +4324,7 @@ function Element:_calculateScrollbarDimensions()
|
|||||||
result.horizontal.thumbWidth = result.horizontal.trackWidth
|
result.horizontal.thumbWidth = result.horizontal.trackWidth
|
||||||
result.horizontal.thumbX = 0
|
result.horizontal.thumbX = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4302,59 +4333,45 @@ end
|
|||||||
function Element:_drawScrollbars(dims)
|
function Element:_drawScrollbars(dims)
|
||||||
local x, y = self.x, self.y
|
local x, y = self.x, self.y
|
||||||
local w, h = self.width, self.height
|
local w, h = self.width, self.height
|
||||||
|
|
||||||
-- Determine thumb color based on state
|
-- Determine thumb color based on state
|
||||||
local thumbColor = self.scrollbarColor
|
local thumbColor = self.scrollbarColor
|
||||||
if self._scrollbarDragging then
|
if self._scrollbarDragging then
|
||||||
-- Active state: brighter
|
-- Active state: brighter
|
||||||
thumbColor = Color.new(
|
thumbColor = Color.new(math.min(1, thumbColor.r * 1.4), math.min(1, thumbColor.g * 1.4), math.min(1, thumbColor.b * 1.4), thumbColor.a)
|
||||||
math.min(1, thumbColor.r * 1.4),
|
|
||||||
math.min(1, thumbColor.g * 1.4),
|
|
||||||
math.min(1, thumbColor.b * 1.4),
|
|
||||||
thumbColor.a
|
|
||||||
)
|
|
||||||
elseif self._scrollbarHovered then
|
elseif self._scrollbarHovered then
|
||||||
-- Hover state: slightly brighter
|
-- Hover state: slightly brighter
|
||||||
thumbColor = Color.new(
|
thumbColor = Color.new(math.min(1, thumbColor.r * 1.2), math.min(1, thumbColor.g * 1.2), math.min(1, thumbColor.b * 1.2), thumbColor.a)
|
||||||
math.min(1, thumbColor.r * 1.2),
|
|
||||||
math.min(1, thumbColor.g * 1.2),
|
|
||||||
math.min(1, thumbColor.b * 1.2),
|
|
||||||
thumbColor.a
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Vertical scrollbar
|
-- Vertical scrollbar
|
||||||
if dims.vertical.visible then
|
if dims.vertical.visible then
|
||||||
local trackX = x + w - self.scrollbarWidth - self.scrollbarPadding + self.padding.left
|
local trackX = x + w - self.scrollbarWidth - self.scrollbarPadding + self.padding.left
|
||||||
local trackY = y + self.scrollbarPadding + self.padding.top
|
local trackY = y + self.scrollbarPadding + self.padding.top
|
||||||
|
|
||||||
-- Draw track
|
-- Draw track
|
||||||
love.graphics.setColor(self.scrollbarTrackColor:toRGBA())
|
love.graphics.setColor(self.scrollbarTrackColor:toRGBA())
|
||||||
love.graphics.rectangle("fill", trackX, trackY,
|
love.graphics.rectangle("fill", trackX, trackY, self.scrollbarWidth, dims.vertical.trackHeight, self.scrollbarRadius)
|
||||||
self.scrollbarWidth, dims.vertical.trackHeight, self.scrollbarRadius)
|
|
||||||
|
|
||||||
-- Draw thumb with state-based color
|
-- Draw thumb with state-based color
|
||||||
love.graphics.setColor(thumbColor:toRGBA())
|
love.graphics.setColor(thumbColor:toRGBA())
|
||||||
love.graphics.rectangle("fill", trackX, trackY + dims.vertical.thumbY,
|
love.graphics.rectangle("fill", trackX, trackY + dims.vertical.thumbY, self.scrollbarWidth, dims.vertical.thumbHeight, self.scrollbarRadius)
|
||||||
self.scrollbarWidth, dims.vertical.thumbHeight, self.scrollbarRadius)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Horizontal scrollbar
|
-- Horizontal scrollbar
|
||||||
if dims.horizontal.visible then
|
if dims.horizontal.visible then
|
||||||
local trackX = x + self.scrollbarPadding + self.padding.left
|
local trackX = x + self.scrollbarPadding + self.padding.left
|
||||||
local trackY = y + h - self.scrollbarWidth - self.scrollbarPadding + self.padding.top
|
local trackY = y + h - self.scrollbarWidth - self.scrollbarPadding + self.padding.top
|
||||||
|
|
||||||
-- Draw track
|
-- Draw track
|
||||||
love.graphics.setColor(self.scrollbarTrackColor:toRGBA())
|
love.graphics.setColor(self.scrollbarTrackColor:toRGBA())
|
||||||
love.graphics.rectangle("fill", trackX, trackY,
|
love.graphics.rectangle("fill", trackX, trackY, dims.horizontal.trackWidth, self.scrollbarWidth, self.scrollbarRadius)
|
||||||
dims.horizontal.trackWidth, self.scrollbarWidth, self.scrollbarRadius)
|
|
||||||
|
|
||||||
-- Draw thumb with state-based color
|
-- Draw thumb with state-based color
|
||||||
love.graphics.setColor(thumbColor:toRGBA())
|
love.graphics.setColor(thumbColor:toRGBA())
|
||||||
love.graphics.rectangle("fill", trackX + dims.horizontal.thumbX, trackY,
|
love.graphics.rectangle("fill", trackX + dims.horizontal.thumbX, trackY, dims.horizontal.thumbWidth, self.scrollbarWidth, self.scrollbarRadius)
|
||||||
dims.horizontal.thumbWidth, self.scrollbarWidth, self.scrollbarRadius)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Reset color
|
-- Reset color
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
end
|
end
|
||||||
@@ -4366,24 +4383,23 @@ end
|
|||||||
function Element:_getScrollbarAtPosition(mouseX, mouseY)
|
function Element:_getScrollbarAtPosition(mouseX, mouseY)
|
||||||
local overflowX = self.overflowX or self.overflow
|
local overflowX = self.overflowX or self.overflow
|
||||||
local overflowY = self.overflowY or self.overflow
|
local overflowY = self.overflowY or self.overflow
|
||||||
|
|
||||||
if not (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") then
|
if not (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local dims = self:_calculateScrollbarDimensions()
|
local dims = self:_calculateScrollbarDimensions()
|
||||||
local x, y = self.x, self.y
|
local x, y = self.x, self.y
|
||||||
local w, h = self.width, self.height
|
local w, h = self.width, self.height
|
||||||
|
|
||||||
-- Check vertical scrollbar
|
-- Check vertical scrollbar
|
||||||
if dims.vertical.visible then
|
if dims.vertical.visible then
|
||||||
local trackX = x + w - self.scrollbarWidth - self.scrollbarPadding + self.padding.left
|
local trackX = x + w - self.scrollbarWidth - self.scrollbarPadding + self.padding.left
|
||||||
local trackY = y + self.scrollbarPadding + self.padding.top
|
local trackY = y + self.scrollbarPadding + self.padding.top
|
||||||
local trackW = self.scrollbarWidth
|
local trackW = self.scrollbarWidth
|
||||||
local trackH = dims.vertical.trackHeight
|
local trackH = dims.vertical.trackHeight
|
||||||
|
|
||||||
if mouseX >= trackX and mouseX <= trackX + trackW and
|
if mouseX >= trackX and mouseX <= trackX + trackW and mouseY >= trackY and mouseY <= trackY + trackH then
|
||||||
mouseY >= trackY and mouseY <= trackY + trackH then
|
|
||||||
-- Check if over thumb
|
-- Check if over thumb
|
||||||
local thumbY = trackY + dims.vertical.thumbY
|
local thumbY = trackY + dims.vertical.thumbY
|
||||||
local thumbH = dims.vertical.thumbHeight
|
local thumbH = dims.vertical.thumbHeight
|
||||||
@@ -4394,16 +4410,15 @@ function Element:_getScrollbarAtPosition(mouseX, mouseY)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check horizontal scrollbar
|
-- Check horizontal scrollbar
|
||||||
if dims.horizontal.visible then
|
if dims.horizontal.visible then
|
||||||
local trackX = x + self.scrollbarPadding + self.padding.left
|
local trackX = x + self.scrollbarPadding + self.padding.left
|
||||||
local trackY = y + h - self.scrollbarWidth - self.scrollbarPadding + self.padding.top
|
local trackY = y + h - self.scrollbarWidth - self.scrollbarPadding + self.padding.top
|
||||||
local trackW = dims.horizontal.trackWidth
|
local trackW = dims.horizontal.trackWidth
|
||||||
local trackH = self.scrollbarWidth
|
local trackH = self.scrollbarWidth
|
||||||
|
|
||||||
if mouseX >= trackX and mouseX <= trackX + trackW and
|
if mouseX >= trackX and mouseX <= trackX + trackW and mouseY >= trackY and mouseY <= trackY + trackH then
|
||||||
mouseY >= trackY and mouseY <= trackY + trackH then
|
|
||||||
-- Check if over thumb
|
-- Check if over thumb
|
||||||
local thumbX = trackX + dims.horizontal.thumbX
|
local thumbX = trackX + dims.horizontal.thumbX
|
||||||
local thumbW = dims.horizontal.thumbWidth
|
local thumbW = dims.horizontal.thumbWidth
|
||||||
@@ -4414,7 +4429,7 @@ function Element:_getScrollbarAtPosition(mouseX, mouseY)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4424,17 +4439,21 @@ end
|
|||||||
---@param button number
|
---@param button number
|
||||||
---@return boolean -- True if event was consumed
|
---@return boolean -- True if event was consumed
|
||||||
function Element:_handleScrollbarPress(mouseX, mouseY, button)
|
function Element:_handleScrollbarPress(mouseX, mouseY, button)
|
||||||
if button ~= 1 then return false end -- Only left click
|
if button ~= 1 then
|
||||||
|
return false
|
||||||
|
end -- Only left click
|
||||||
|
|
||||||
local scrollbar = self:_getScrollbarAtPosition(mouseX, mouseY)
|
local scrollbar = self:_getScrollbarAtPosition(mouseX, mouseY)
|
||||||
if not scrollbar then return false end
|
if not scrollbar then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
if scrollbar.region == "thumb" then
|
if scrollbar.region == "thumb" then
|
||||||
-- Start dragging thumb
|
-- Start dragging thumb
|
||||||
self._scrollbarDragging = true
|
self._scrollbarDragging = true
|
||||||
self._hoveredScrollbar = scrollbar.component
|
self._hoveredScrollbar = scrollbar.component
|
||||||
local dims = self:_calculateScrollbarDimensions()
|
local dims = self:_calculateScrollbarDimensions()
|
||||||
|
|
||||||
if scrollbar.component == "vertical" then
|
if scrollbar.component == "vertical" then
|
||||||
local trackY = self.y + self.scrollbarPadding + self.padding.top
|
local trackY = self.y + self.scrollbarPadding + self.padding.top
|
||||||
local thumbY = trackY + dims.vertical.thumbY
|
local thumbY = trackY + dims.vertical.thumbY
|
||||||
@@ -4444,15 +4463,14 @@ function Element:_handleScrollbarPress(mouseX, mouseY, button)
|
|||||||
local thumbX = trackX + dims.horizontal.thumbX
|
local thumbX = trackX + dims.horizontal.thumbX
|
||||||
self._scrollbarDragOffset = mouseX - thumbX
|
self._scrollbarDragOffset = mouseX - thumbX
|
||||||
end
|
end
|
||||||
|
|
||||||
return true -- Event consumed
|
return true -- Event consumed
|
||||||
|
|
||||||
elseif scrollbar.region == "track" then
|
elseif scrollbar.region == "track" then
|
||||||
-- Click on track - jump to position
|
-- Click on track - jump to position
|
||||||
self:_scrollToTrackPosition(mouseX, mouseY, scrollbar.component)
|
self:_scrollToTrackPosition(mouseX, mouseY, scrollbar.component)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4461,43 +4479,44 @@ end
|
|||||||
---@param mouseY number
|
---@param mouseY number
|
||||||
---@return boolean -- True if event was consumed
|
---@return boolean -- True if event was consumed
|
||||||
function Element:_handleScrollbarDrag(mouseX, mouseY)
|
function Element:_handleScrollbarDrag(mouseX, mouseY)
|
||||||
if not self._scrollbarDragging then return false end
|
if not self._scrollbarDragging then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local dims = self:_calculateScrollbarDimensions()
|
local dims = self:_calculateScrollbarDimensions()
|
||||||
|
|
||||||
if self._hoveredScrollbar == "vertical" then
|
if self._hoveredScrollbar == "vertical" then
|
||||||
local trackY = self.y + self.scrollbarPadding + self.padding.top
|
local trackY = self.y + self.scrollbarPadding + self.padding.top
|
||||||
local trackH = dims.vertical.trackHeight
|
local trackH = dims.vertical.trackHeight
|
||||||
local thumbH = dims.vertical.thumbHeight
|
local thumbH = dims.vertical.thumbHeight
|
||||||
|
|
||||||
-- Calculate new thumb position
|
-- Calculate new thumb position
|
||||||
local newThumbY = mouseY - self._scrollbarDragOffset - trackY
|
local newThumbY = mouseY - self._scrollbarDragOffset - trackY
|
||||||
newThumbY = math.max(0, math.min(newThumbY, trackH - thumbH))
|
newThumbY = math.max(0, math.min(newThumbY, trackH - thumbH))
|
||||||
|
|
||||||
-- Convert thumb position to scroll position
|
-- Convert thumb position to scroll position
|
||||||
local scrollRatio = (trackH - thumbH) > 0 and (newThumbY / (trackH - thumbH)) or 0
|
local scrollRatio = (trackH - thumbH) > 0 and (newThumbY / (trackH - thumbH)) or 0
|
||||||
local newScrollY = scrollRatio * self._maxScrollY
|
local newScrollY = scrollRatio * self._maxScrollY
|
||||||
|
|
||||||
self:setScrollPosition(nil, newScrollY)
|
self:setScrollPosition(nil, newScrollY)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
elseif self._hoveredScrollbar == "horizontal" then
|
elseif self._hoveredScrollbar == "horizontal" then
|
||||||
local trackX = self.x + self.scrollbarPadding + self.padding.left
|
local trackX = self.x + self.scrollbarPadding + self.padding.left
|
||||||
local trackW = dims.horizontal.trackWidth
|
local trackW = dims.horizontal.trackWidth
|
||||||
local thumbW = dims.horizontal.thumbWidth
|
local thumbW = dims.horizontal.thumbWidth
|
||||||
|
|
||||||
-- Calculate new thumb position
|
-- Calculate new thumb position
|
||||||
local newThumbX = mouseX - self._scrollbarDragOffset - trackX
|
local newThumbX = mouseX - self._scrollbarDragOffset - trackX
|
||||||
newThumbX = math.max(0, math.min(newThumbX, trackW - thumbW))
|
newThumbX = math.max(0, math.min(newThumbX, trackW - thumbW))
|
||||||
|
|
||||||
-- Convert thumb position to scroll position
|
-- Convert thumb position to scroll position
|
||||||
local scrollRatio = (trackW - thumbW) > 0 and (newThumbX / (trackW - thumbW)) or 0
|
local scrollRatio = (trackW - thumbW) > 0 and (newThumbX / (trackW - thumbW)) or 0
|
||||||
local newScrollX = scrollRatio * self._maxScrollX
|
local newScrollX = scrollRatio * self._maxScrollX
|
||||||
|
|
||||||
self:setScrollPosition(newScrollX, nil)
|
self:setScrollPosition(newScrollX, nil)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4505,13 +4524,15 @@ end
|
|||||||
---@param button number
|
---@param button number
|
||||||
---@return boolean -- True if event was consumed
|
---@return boolean -- True if event was consumed
|
||||||
function Element:_handleScrollbarRelease(button)
|
function Element:_handleScrollbarRelease(button)
|
||||||
if button ~= 1 then return false end
|
if button ~= 1 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
if self._scrollbarDragging then
|
if self._scrollbarDragging then
|
||||||
self._scrollbarDragging = false
|
self._scrollbarDragging = false
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4521,35 +4542,34 @@ end
|
|||||||
---@param component string -- "vertical" or "horizontal"
|
---@param component string -- "vertical" or "horizontal"
|
||||||
function Element:_scrollToTrackPosition(mouseX, mouseY, component)
|
function Element:_scrollToTrackPosition(mouseX, mouseY, component)
|
||||||
local dims = self:_calculateScrollbarDimensions()
|
local dims = self:_calculateScrollbarDimensions()
|
||||||
|
|
||||||
if component == "vertical" then
|
if component == "vertical" then
|
||||||
local trackY = self.y + self.scrollbarPadding + self.padding.top
|
local trackY = self.y + self.scrollbarPadding + self.padding.top
|
||||||
local trackH = dims.vertical.trackHeight
|
local trackH = dims.vertical.trackHeight
|
||||||
local thumbH = dims.vertical.thumbHeight
|
local thumbH = dims.vertical.thumbHeight
|
||||||
|
|
||||||
-- Calculate target thumb position (centered on click)
|
-- Calculate target thumb position (centered on click)
|
||||||
local targetThumbY = mouseY - trackY - (thumbH / 2)
|
local targetThumbY = mouseY - trackY - (thumbH / 2)
|
||||||
targetThumbY = math.max(0, math.min(targetThumbY, trackH - thumbH))
|
targetThumbY = math.max(0, math.min(targetThumbY, trackH - thumbH))
|
||||||
|
|
||||||
-- Convert to scroll position
|
-- Convert to scroll position
|
||||||
local scrollRatio = (trackH - thumbH) > 0 and (targetThumbY / (trackH - thumbH)) or 0
|
local scrollRatio = (trackH - thumbH) > 0 and (targetThumbY / (trackH - thumbH)) or 0
|
||||||
local newScrollY = scrollRatio * self._maxScrollY
|
local newScrollY = scrollRatio * self._maxScrollY
|
||||||
|
|
||||||
self:setScrollPosition(nil, newScrollY)
|
self:setScrollPosition(nil, newScrollY)
|
||||||
|
|
||||||
elseif component == "horizontal" then
|
elseif component == "horizontal" then
|
||||||
local trackX = self.x + self.scrollbarPadding + self.padding.left
|
local trackX = self.x + self.scrollbarPadding + self.padding.left
|
||||||
local trackW = dims.horizontal.trackWidth
|
local trackW = dims.horizontal.trackWidth
|
||||||
local thumbW = dims.horizontal.thumbWidth
|
local thumbW = dims.horizontal.thumbWidth
|
||||||
|
|
||||||
-- Calculate target thumb position (centered on click)
|
-- Calculate target thumb position (centered on click)
|
||||||
local targetThumbX = mouseX - trackX - (thumbW / 2)
|
local targetThumbX = mouseX - trackX - (thumbW / 2)
|
||||||
targetThumbX = math.max(0, math.min(targetThumbX, trackW - thumbW))
|
targetThumbX = math.max(0, math.min(targetThumbX, trackW - thumbW))
|
||||||
|
|
||||||
-- Convert to scroll position
|
-- Convert to scroll position
|
||||||
local scrollRatio = (trackW - thumbW) > 0 and (targetThumbX / (trackW - thumbW)) or 0
|
local scrollRatio = (trackW - thumbW) > 0 and (targetThumbX / (trackW - thumbW)) or 0
|
||||||
local newScrollX = scrollRatio * self._maxScrollX
|
local newScrollX = scrollRatio * self._maxScrollX
|
||||||
|
|
||||||
self:setScrollPosition(newScrollX, nil)
|
self:setScrollPosition(newScrollX, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4561,24 +4581,24 @@ end
|
|||||||
function Element:_handleWheelScroll(x, y)
|
function Element:_handleWheelScroll(x, y)
|
||||||
local overflowX = self.overflowX or self.overflow
|
local overflowX = self.overflowX or self.overflow
|
||||||
local overflowY = self.overflowY or self.overflow
|
local overflowY = self.overflowY or self.overflow
|
||||||
|
|
||||||
if not (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") then
|
if not (overflowX == "scroll" or overflowX == "auto" or overflowY == "scroll" or overflowY == "auto") then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local hasVerticalOverflow = self._overflowY and self._maxScrollY > 0
|
local hasVerticalOverflow = self._overflowY and self._maxScrollY > 0
|
||||||
local hasHorizontalOverflow = self._overflowX and self._maxScrollX > 0
|
local hasHorizontalOverflow = self._overflowX and self._maxScrollX > 0
|
||||||
|
|
||||||
local scrolled = false
|
local scrolled = false
|
||||||
|
|
||||||
-- Vertical scrolling
|
-- Vertical scrolling
|
||||||
if y ~= 0 and hasVerticalOverflow then
|
if y ~= 0 and hasVerticalOverflow then
|
||||||
local delta = -y * self.scrollSpeed -- Negative because wheel up = scroll up
|
local delta = -y * self.scrollSpeed -- Negative because wheel up = scroll up
|
||||||
local newScrollY = self._scrollY + delta
|
local newScrollY = self._scrollY + delta
|
||||||
self:setScrollPosition(nil, newScrollY)
|
self:setScrollPosition(nil, newScrollY)
|
||||||
scrolled = true
|
scrolled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Horizontal scrolling
|
-- Horizontal scrolling
|
||||||
if x ~= 0 and hasHorizontalOverflow then
|
if x ~= 0 and hasHorizontalOverflow then
|
||||||
local delta = -x * self.scrollSpeed
|
local delta = -x * self.scrollSpeed
|
||||||
@@ -4586,7 +4606,7 @@ function Element:_handleWheelScroll(x, y)
|
|||||||
self:setScrollPosition(newScrollX, nil)
|
self:setScrollPosition(newScrollX, nil)
|
||||||
scrolled = true
|
scrolled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
return scrolled
|
return scrolled
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -5251,7 +5271,7 @@ function Element:layoutChildren()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Detect overflow after children are laid out
|
-- Detect overflow after children are laid out
|
||||||
self:_detectOverflow()
|
self:_detectOverflow()
|
||||||
end
|
end
|
||||||
@@ -5339,14 +5359,16 @@ function Element:draw(backdropCanvas)
|
|||||||
local imageY = self.y + self.padding.top
|
local imageY = self.y + self.padding.top
|
||||||
local imageWidth = self.width
|
local imageWidth = self.width
|
||||||
local imageHeight = self.height
|
local imageHeight = self.height
|
||||||
|
|
||||||
-- Combine element opacity with imageOpacity
|
-- Combine element opacity with imageOpacity
|
||||||
local finalOpacity = self.opacity * self.imageOpacity
|
local finalOpacity = self.opacity * self.imageOpacity
|
||||||
|
|
||||||
-- Apply cornerRadius clipping if set
|
-- Apply cornerRadius clipping if set
|
||||||
local hasCornerRadius = self.cornerRadius.topLeft > 0 or self.cornerRadius.topRight > 0
|
local hasCornerRadius = self.cornerRadius.topLeft > 0
|
||||||
or self.cornerRadius.bottomLeft > 0 or self.cornerRadius.bottomRight > 0
|
or self.cornerRadius.topRight > 0
|
||||||
|
or self.cornerRadius.bottomLeft > 0
|
||||||
|
or self.cornerRadius.bottomRight > 0
|
||||||
|
|
||||||
if hasCornerRadius then
|
if hasCornerRadius then
|
||||||
-- Use stencil to clip image to rounded corners
|
-- Use stencil to clip image to rounded corners
|
||||||
love.graphics.stencil(function()
|
love.graphics.stencil(function()
|
||||||
@@ -5354,19 +5376,10 @@ function Element:draw(backdropCanvas)
|
|||||||
end, "replace", 1)
|
end, "replace", 1)
|
||||||
love.graphics.setStencilTest("greater", 0)
|
love.graphics.setStencilTest("greater", 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw the image
|
-- Draw the image
|
||||||
ImageRenderer.draw(
|
ImageRenderer.draw(self._loadedImage, imageX, imageY, imageWidth, imageHeight, self.objectFit, self.objectPosition, finalOpacity)
|
||||||
self._loadedImage,
|
|
||||||
imageX,
|
|
||||||
imageY,
|
|
||||||
imageWidth,
|
|
||||||
imageHeight,
|
|
||||||
self.objectFit,
|
|
||||||
self.objectPosition,
|
|
||||||
finalOpacity
|
|
||||||
)
|
|
||||||
|
|
||||||
-- Clear stencil if it was used
|
-- Clear stencil if it was used
|
||||||
if hasCornerRadius then
|
if hasCornerRadius then
|
||||||
love.graphics.setStencilTest()
|
love.graphics.setStencilTest()
|
||||||
@@ -5529,10 +5542,10 @@ function Element:draw(backdropCanvas)
|
|||||||
elseif self.textAlign == TextAlign.JUSTIFY then
|
elseif self.textAlign == TextAlign.JUSTIFY then
|
||||||
align = "justify"
|
align = "justify"
|
||||||
end
|
end
|
||||||
|
|
||||||
tx = contentX
|
tx = contentX
|
||||||
ty = contentY
|
ty = contentY
|
||||||
|
|
||||||
-- Use printf with the available width for wrapping
|
-- Use printf with the available width for wrapping
|
||||||
love.graphics.printf(self.text, tx, ty, textAreaWidth, align)
|
love.graphics.printf(self.text, tx, ty, textAreaWidth, align)
|
||||||
else
|
else
|
||||||
@@ -5598,15 +5611,15 @@ function Element:draw(backdropCanvas)
|
|||||||
local overflowX = self.overflowX or self.overflow
|
local overflowX = self.overflowX or self.overflow
|
||||||
local overflowY = self.overflowY or self.overflow
|
local overflowY = self.overflowY or self.overflow
|
||||||
local needsOverflowClipping = (overflowX ~= "visible" or overflowY ~= "visible") and (overflowX ~= nil or overflowY ~= nil)
|
local needsOverflowClipping = (overflowX ~= "visible" or overflowY ~= "visible") and (overflowX ~= nil or overflowY ~= nil)
|
||||||
|
|
||||||
-- Apply scroll offset if overflow is not visible
|
-- Apply scroll offset if overflow is not visible
|
||||||
local hasScrollOffset = needsOverflowClipping and (self._scrollX ~= 0 or self._scrollY ~= 0)
|
local hasScrollOffset = needsOverflowClipping and (self._scrollX ~= 0 or self._scrollY ~= 0)
|
||||||
|
|
||||||
if hasScrollOffset then
|
if hasScrollOffset then
|
||||||
love.graphics.push()
|
love.graphics.push()
|
||||||
love.graphics.translate(-self._scrollX, -self._scrollY)
|
love.graphics.translate(-self._scrollX, -self._scrollY)
|
||||||
end
|
end
|
||||||
|
|
||||||
if hasRoundedCorners and #sortedChildren > 0 then
|
if hasRoundedCorners and #sortedChildren > 0 then
|
||||||
-- Use stencil to clip children to rounded rectangle
|
-- Use stencil to clip children to rounded rectangle
|
||||||
-- BORDER-BOX MODEL: Use stored border-box dimensions for clipping
|
-- BORDER-BOX MODEL: Use stored border-box dimensions for clipping
|
||||||
@@ -5628,13 +5641,13 @@ function Element:draw(backdropCanvas)
|
|||||||
local contentY = self.y + self.padding.top
|
local contentY = self.y + self.padding.top
|
||||||
local contentWidth = self.width
|
local contentWidth = self.width
|
||||||
local contentHeight = self.height
|
local contentHeight = self.height
|
||||||
|
|
||||||
love.graphics.setScissor(contentX, contentY, contentWidth, contentHeight)
|
love.graphics.setScissor(contentX, contentY, contentWidth, contentHeight)
|
||||||
|
|
||||||
for _, child in ipairs(sortedChildren) do
|
for _, child in ipairs(sortedChildren) do
|
||||||
child:draw(backdropCanvas)
|
child:draw(backdropCanvas)
|
||||||
end
|
end
|
||||||
|
|
||||||
love.graphics.setScissor()
|
love.graphics.setScissor()
|
||||||
else
|
else
|
||||||
-- No clipping needed
|
-- No clipping needed
|
||||||
@@ -5642,7 +5655,7 @@ function Element:draw(backdropCanvas)
|
|||||||
child:draw(backdropCanvas)
|
child:draw(backdropCanvas)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if hasScrollOffset then
|
if hasScrollOffset then
|
||||||
love.graphics.pop()
|
love.graphics.pop()
|
||||||
end
|
end
|
||||||
@@ -5659,7 +5672,7 @@ function Element:draw(backdropCanvas)
|
|||||||
else
|
else
|
||||||
drawChildren()
|
drawChildren()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw scrollbars if overflow is scroll or auto
|
-- Draw scrollbars if overflow is scroll or auto
|
||||||
local overflowX = self.overflowX or self.overflow
|
local overflowX = self.overflowX or self.overflow
|
||||||
local overflowY = self.overflowY or self.overflow
|
local overflowY = self.overflowY or self.overflow
|
||||||
@@ -5718,7 +5731,7 @@ function Element:update(dt)
|
|||||||
self._hoveredScrollbar = nil
|
self._hoveredScrollbar = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle scrollbar dragging
|
-- Handle scrollbar dragging
|
||||||
if self._scrollbarDragging and love.mouse.isDown(1) then
|
if self._scrollbarDragging and love.mouse.isDown(1) then
|
||||||
self:_handleScrollbarDrag(mx, my)
|
self:_handleScrollbarDrag(mx, my)
|
||||||
@@ -5795,7 +5808,7 @@ function Element:update(dt)
|
|||||||
self.callback(self, pressEvent)
|
self.callback(self, pressEvent)
|
||||||
self._pressed[button] = true
|
self._pressed[button] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Record drag start position per button
|
-- Record drag start position per button
|
||||||
self._dragStartX[button] = mx
|
self._dragStartX[button] = mx
|
||||||
self._dragStartY[button] = my
|
self._dragStartY[button] = my
|
||||||
@@ -5805,13 +5818,13 @@ function Element:update(dt)
|
|||||||
-- Button is still pressed - check for mouse movement (drag)
|
-- Button is still pressed - check for mouse movement (drag)
|
||||||
local lastX = self._lastMouseX[button] or mx
|
local lastX = self._lastMouseX[button] or mx
|
||||||
local lastY = self._lastMouseY[button] or my
|
local lastY = self._lastMouseY[button] or my
|
||||||
|
|
||||||
if lastX ~= mx or lastY ~= my then
|
if lastX ~= mx or lastY ~= my then
|
||||||
-- Mouse has moved - fire drag event
|
-- Mouse has moved - fire drag event
|
||||||
local modifiers = getModifiers()
|
local modifiers = getModifiers()
|
||||||
local dx = mx - self._dragStartX[button]
|
local dx = mx - self._dragStartX[button]
|
||||||
local dy = my - self._dragStartY[button]
|
local dy = my - self._dragStartY[button]
|
||||||
|
|
||||||
local dragEvent = InputEvent.new({
|
local dragEvent = InputEvent.new({
|
||||||
type = "drag",
|
type = "drag",
|
||||||
button = button,
|
button = button,
|
||||||
@@ -5823,7 +5836,7 @@ function Element:update(dt)
|
|||||||
clickCount = 1,
|
clickCount = 1,
|
||||||
})
|
})
|
||||||
self.callback(self, dragEvent)
|
self.callback(self, dragEvent)
|
||||||
|
|
||||||
-- Update last known position for this button
|
-- Update last known position for this button
|
||||||
self._lastMouseX[button] = mx
|
self._lastMouseX[button] = mx
|
||||||
self._lastMouseY[button] = my
|
self._lastMouseY[button] = my
|
||||||
@@ -5867,7 +5880,7 @@ function Element:update(dt)
|
|||||||
|
|
||||||
self.callback(self, clickEvent)
|
self.callback(self, clickEvent)
|
||||||
self._pressed[button] = false
|
self._pressed[button] = false
|
||||||
|
|
||||||
-- Clean up drag tracking
|
-- Clean up drag tracking
|
||||||
self._dragStartX[button] = nil
|
self._dragStartX[button] = nil
|
||||||
self._dragStartY[button] = nil
|
self._dragStartY[button] = nil
|
||||||
@@ -6174,7 +6187,7 @@ function Element:recalculateUnits(newViewportWidth, newViewportHeight)
|
|||||||
self._borderBoxHeight = self.height + self.padding.top + self.padding.bottom
|
self._borderBoxHeight = self.height + self.padding.top + self.padding.bottom
|
||||||
end
|
end
|
||||||
-- For pixel units, height stays as-is (may have been manually modified)
|
-- For pixel units, height stays as-is (may have been manually modified)
|
||||||
|
|
||||||
-- Detect overflow after layout calculations
|
-- Detect overflow after layout calculations
|
||||||
self:_detectOverflow()
|
self:_detectOverflow()
|
||||||
end
|
end
|
||||||
@@ -6348,7 +6361,7 @@ function Element:calculateTextHeight()
|
|||||||
if self.contentAutoSizingMultiplier and self.contentAutoSizingMultiplier.height then
|
if self.contentAutoSizingMultiplier and self.contentAutoSizingMultiplier.height then
|
||||||
height = height * self.contentAutoSizingMultiplier.height
|
height = height * self.contentAutoSizingMultiplier.height
|
||||||
end
|
end
|
||||||
|
|
||||||
return height
|
return height
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user