checking logic

This commit is contained in:
Michael Freno
2025-10-31 12:18:39 -04:00
parent 9f215e252e
commit 747382614b

View File

@@ -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
@@ -966,7 +966,7 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
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,7 +988,6 @@ 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)
@@ -1016,7 +1014,6 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
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()
@@ -2591,14 +2612,15 @@ function Gui.wheelmoved(x, y)
-- 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
@@ -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()
@@ -4246,7 +4277,7 @@ 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
@@ -4307,20 +4338,10 @@ function Element:_drawScrollbars(dims)
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
@@ -4330,13 +4351,11 @@ function Element:_drawScrollbars(dims)
-- 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
@@ -4346,13 +4365,11 @@ function Element:_drawScrollbars(dims)
-- 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
@@ -4382,8 +4399,7 @@ function Element:_getScrollbarAtPosition(mouseX, mouseY)
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
@@ -4402,8 +4418,7 @@ function Element:_getScrollbarAtPosition(mouseX, mouseY)
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
@@ -4424,10 +4439,14 @@ 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
@@ -4446,7 +4465,6 @@ function Element:_handleScrollbarPress(mouseX, mouseY, button)
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)
@@ -4461,7 +4479,9 @@ 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()
@@ -4480,7 +4500,6 @@ function Element:_handleScrollbarDrag(mouseX, mouseY)
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
@@ -4505,7 +4524,9 @@ 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
@@ -4536,7 +4557,6 @@ function Element:_scrollToTrackPosition(mouseX, mouseY, component)
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
@@ -5344,8 +5364,10 @@ function Element:draw(backdropCanvas)
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
@@ -5356,16 +5378,7 @@ function Element:draw(backdropCanvas)
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