flesh out Calc, with lsp support

This commit is contained in:
Michael Freno
2025-12-07 11:14:12 -05:00
parent 4f60e00b2e
commit 609a54b4f1
5 changed files with 719 additions and 53 deletions

View File

@@ -4,7 +4,7 @@
local Calc = {}
--- Initialize Calc module with dependencies
---@param deps table Dependencies: { ErrorHandler = table? }
---@param deps CalcDependencies Dependencies: { ErrorHandler = ErrorHandler? }
function Calc.init(deps)
Calc._ErrorHandler = deps.ErrorHandler
end
@@ -24,7 +24,7 @@ local TokenType = {
--- Tokenize a calc expression string into tokens
---@param expr string The expression to tokenize (e.g., "50% - 10vw")
---@return table|nil tokens Array of tokens with type, value, unit
---@return CalcToken[]? tokens Array of tokens with type, value, unit
---@return string? error Error message if tokenization fails
local function tokenize(expr)
local tokens = {}
@@ -157,13 +157,13 @@ end
--- Parser for calc expressions using recursive descent
---@class Parser
---@field tokens table Array of tokens
---@field tokens CalcToken[] Array of tokens
---@field pos number Current token position
local Parser = {}
Parser.__index = Parser
--- Create a new parser
---@param tokens table Array of tokens
---@param tokens CalcToken[] Array of tokens
---@return Parser
function Parser.new(tokens)
local self = setmetatable({}, Parser)
@@ -173,7 +173,7 @@ function Parser.new(tokens)
end
--- Get current token
---@return table token Current token
---@return CalcToken token Current token
function Parser:current()
return self.tokens[self.pos]
end
@@ -184,7 +184,7 @@ function Parser:advance()
end
--- Parse expression (handles + and -)
---@return table ast Abstract syntax tree node
---@return CalcASTNode ast Abstract syntax tree node
function Parser:parseExpression()
local left = self:parseTerm()
@@ -203,7 +203,7 @@ function Parser:parseExpression()
end
--- Parse term (handles * and /)
---@return table ast Abstract syntax tree node
---@return CalcASTNode ast Abstract syntax tree node
function Parser:parseTerm()
local left = self:parseFactor()
@@ -222,7 +222,7 @@ function Parser:parseTerm()
end
--- Parse factor (handles numbers and parentheses)
---@return table ast Abstract syntax tree node
---@return CalcASTNode ast Abstract syntax tree node
function Parser:parseFactor()
local token = self:current()
@@ -247,7 +247,7 @@ function Parser:parseFactor()
end
--- Parse the tokens into an AST
---@return table ast Abstract syntax tree
---@return CalcASTNode ast Abstract syntax tree
function Parser:parse()
local ast = self:parseExpression()
if self:current().type ~= TokenType.EOF then
@@ -259,7 +259,7 @@ end
--- Create a calc expression object that can be resolved later
--- This is the main API function that users call
---@param expr string The calc expression (e.g., "50% - 10vw")
---@return table calcObject A calc expression object with AST
---@return CalcObject calcObject A calc expression object with AST
function Calc.new(expr)
-- Tokenize
local tokens, err = tokenize(expr)
@@ -316,7 +316,7 @@ function Calc.isCalc(value)
end
--- Resolve a calc expression to pixel value
---@param calcObj table The calc expression object
---@param calcObj CalcObject The calc expression object
---@param viewportWidth number Viewport width in pixels
---@param viewportHeight number Viewport height in pixels
---@param parentSize number? Parent dimension for percentage units

View File

@@ -162,6 +162,7 @@ function Element.init(deps)
Element._Color = deps.Color
Element._Context = deps.Context
Element._Units = deps.Units
Element._Calc = deps.Calc
Element._utils = deps.utils
Element._InputEvent = deps.InputEvent
Element._EventHandler = deps.EventHandler
@@ -826,11 +827,22 @@ function Element.new(props)
local widthProp = props.width
local tempWidth = 0 -- Temporary width for padding resolution
if widthProp then
if type(widthProp) == "string" then
-- Check if it's a CalcObject (table with _isCalc marker)
local isCalc = Element._Calc and Element._Calc.isCalc(widthProp)
if type(widthProp) == "string" or isCalc then
local value, unit = Element._Units.parse(widthProp)
self.units.width = { value = value, unit = unit }
local parentWidth = self.parent and self.parent.width or viewportWidth
tempWidth = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth)
-- Defensive check: ensure tempWidth is a number after resolution
if type(tempWidth) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "width resolution returned non-number value",
type = type(tempWidth),
value = tostring(tempWidth),
})
tempWidth = 0
end
else
tempWidth = Element._Context.baseScale and (widthProp * scaleX) or widthProp
self.units.width = { value = widthProp, unit = "px" }
@@ -856,11 +868,22 @@ function Element.new(props)
local heightProp = props.height
local tempHeight = 0 -- Temporary height for padding resolution
if heightProp then
if type(heightProp) == "string" then
-- Check if it's a CalcObject (table with _isCalc marker)
local isCalc = Element._Calc and Element._Calc.isCalc(heightProp)
if type(heightProp) == "string" or isCalc then
local value, unit = Element._Units.parse(heightProp)
self.units.height = { value = value, unit = unit }
local parentHeight = self.parent and self.parent.height or viewportHeight
tempHeight = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
-- Defensive check: ensure tempHeight is a number after resolution
if type(tempHeight) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "height resolution returned non-number value",
type = type(tempHeight),
value = tostring(tempHeight),
})
tempHeight = 0
end
else
-- Apply base scaling to pixel values
tempHeight = Element._Context.baseScale and (heightProp * scaleY) or heightProp
@@ -877,14 +900,25 @@ function Element.new(props)
--- child positioning ---
if props.gap then
if type(props.gap) == "string" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.gap)
if type(props.gap) == "string" or isCalc then
local value, unit = Element._Units.parse(props.gap)
self.units.gap = { value = value, unit = unit }
-- Gap percentages should be relative to the element's own size, not parent
-- For horizontal flex, gap is based on width; for vertical flex, based on height
local flexDir = props.flexDirection or Element._utils.enums.FlexDirection.HORIZONTAL
local containerSize = (flexDir == Element._utils.enums.FlexDirection.HORIZONTAL) and self.width or self.height
self.gap = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, containerSize)
local resolvedGap = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, containerSize)
-- Defensive check: ensure gap is a number after resolution
if type(resolvedGap) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "gap resolution returned non-number value",
type = type(resolvedGap),
value = tostring(resolvedGap),
})
resolvedGap = 0
end
self.gap = resolvedGap
else
self.gap = props.gap
self.units.gap = { value = props.gap, unit = "px" }
@@ -925,6 +959,27 @@ function Element.new(props)
-- For auto-sized elements, this is content width; for explicit sizing, this is border-box width
local tempPadding
if use9PatchPadding then
-- Ensure tempWidth and tempHeight are numbers (not CalcObjects)
-- This should already be true after Units.resolve(), but add defensive check
if type(tempWidth) ~= "number" then
if Element._ErrorHandler then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "tempWidth is not a number after resolution",
type = type(tempWidth),
})
end
tempWidth = 0
end
if type(tempHeight) ~= "number" then
if Element._ErrorHandler then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "tempHeight is not a number after resolution",
type = type(tempHeight),
})
end
tempHeight = 0
end
-- Get scaled 9-patch content padding from ThemeManager
local scaledPadding = self._themeManager:getScaledContentPadding(tempWidth, tempHeight)
if scaledPadding then
@@ -1092,10 +1147,19 @@ function Element.new(props)
-- Handle x position with units
if props.x then
if type(props.x) == "string" or type(props.x) == "table" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.x)
if type(props.x) == "string" or isCalc then
local value, unit = Element._Units.parse(props.x)
self.units.x = { value = value, unit = unit }
self.x = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
local resolvedX = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
if type(resolvedX) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "x resolution returned non-number value",
type = type(resolvedX),
})
resolvedX = 0
end
self.x = resolvedX
else
-- Apply base scaling to pixel positions
self.x = Element._Context.baseScale and (props.x * scaleX) or props.x
@@ -1108,10 +1172,19 @@ function Element.new(props)
-- Handle y position with units
if props.y then
if type(props.y) == "string" or type(props.y) == "table" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.y)
if type(props.y) == "string" or isCalc then
local value, unit = Element._Units.parse(props.y)
self.units.y = { value = value, unit = unit }
self.y = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
local resolvedY = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
if type(resolvedY) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "y resolution returned non-number value",
type = type(resolvedY),
})
resolvedY = 0
end
self.y = resolvedY
else
-- Apply base scaling to pixel positions
self.y = Element._Context.baseScale and (props.y * scaleY) or props.y
@@ -1181,11 +1254,19 @@ function Element.new(props)
-- Handle x position with units
if props.x then
if type(props.x) == "string" or type(props.x) == "table" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.x)
if type(props.x) == "string" or isCalc then
local value, unit = Element._Units.parse(props.x)
self.units.x = { value = value, unit = unit }
local parentWidth = self.parent.width
local offsetX = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth)
if type(offsetX) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "x resolution returned non-number value",
type = type(offsetX),
})
offsetX = 0
end
self.x = baseX + offsetX
else
-- Apply base scaling to pixel positions
@@ -1200,11 +1281,19 @@ function Element.new(props)
-- Handle y position with units
if props.y then
if type(props.y) == "string" or type(props.y) == "table" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.y)
if type(props.y) == "string" or isCalc then
local value, unit = Element._Units.parse(props.y)
self.units.y = { value = value, unit = unit }
local parentHeight = self.parent.height
local offsetY = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
if type(offsetY) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "y resolution returned non-number value",
type = type(offsetY),
})
offsetY = 0
end
self.y = baseY + offsetY
else
-- Apply base scaling to pixel positions
@@ -1225,11 +1314,19 @@ function Element.new(props)
local baseY = self.parent.y + self.parent.padding.top
if props.x then
if type(props.x) == "string" or type(props.x) == "table" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.x)
if type(props.x) == "string" or isCalc then
local value, unit = Element._Units.parse(props.x)
self.units.x = { value = value, unit = unit }
local parentWidth = self.parent.width
local offsetX = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth)
if type(offsetX) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "x resolution returned non-number value",
type = type(offsetX),
})
offsetX = 0
end
self.x = baseX + offsetX
else
-- Apply base scaling to pixel offsets
@@ -1243,11 +1340,19 @@ function Element.new(props)
end
if props.y then
if type(props.y) == "string" or type(props.y) == "table" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.y)
if type(props.y) == "string" or isCalc then
local value, unit = Element._Units.parse(props.y)
self.units.y = { value = value, unit = unit }
parentHeight = self.parent.height
local offsetY = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
if type(offsetY) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "y resolution returned non-number value",
type = type(offsetY),
})
offsetY = 0
end
self.y = baseY + offsetY
else
-- Apply base scaling to pixel offsets
@@ -1283,10 +1388,19 @@ function Element.new(props)
-- Handle positioning properties for ALL elements (with or without parent)
-- Handle top positioning with units
if props.top then
if type(props.top) == "string" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.top)
if type(props.top) == "string" or isCalc then
local value, unit = Element._Units.parse(props.top)
self.units.top = { value = value, unit = unit }
self.top = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
local resolvedTop = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
if type(resolvedTop) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "top resolution returned non-number value",
type = type(resolvedTop),
})
resolvedTop = 0
end
self.top = resolvedTop
else
self.top = props.top
self.units.top = { value = props.top, unit = "px" }
@@ -1298,10 +1412,19 @@ function Element.new(props)
-- Handle right positioning with units
if props.right then
if type(props.right) == "string" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.right)
if type(props.right) == "string" or isCalc then
local value, unit = Element._Units.parse(props.right)
self.units.right = { value = value, unit = unit }
self.right = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
local resolvedRight = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
if type(resolvedRight) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "right resolution returned non-number value",
type = type(resolvedRight),
})
resolvedRight = 0
end
self.right = resolvedRight
else
self.right = props.right
self.units.right = { value = props.right, unit = "px" }
@@ -1313,10 +1436,19 @@ function Element.new(props)
-- Handle bottom positioning with units
if props.bottom then
if type(props.bottom) == "string" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.bottom)
if type(props.bottom) == "string" or isCalc then
local value, unit = Element._Units.parse(props.bottom)
self.units.bottom = { value = value, unit = unit }
self.bottom = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
local resolvedBottom = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
if type(resolvedBottom) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "bottom resolution returned non-number value",
type = type(resolvedBottom),
})
resolvedBottom = 0
end
self.bottom = resolvedBottom
else
self.bottom = props.bottom
self.units.bottom = { value = props.bottom, unit = "px" }
@@ -1328,10 +1460,19 @@ function Element.new(props)
-- Handle left positioning with units
if props.left then
if type(props.left) == "string" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.left)
if type(props.left) == "string" or isCalc then
local value, unit = Element._Units.parse(props.left)
self.units.left = { value = value, unit = unit }
self.left = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
local resolvedLeft = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
if type(resolvedLeft) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "left resolution returned non-number value",
type = type(resolvedLeft),
})
resolvedLeft = 0
end
self.left = resolvedLeft
else
self.left = props.left
self.units.left = { value = props.left, unit = "px" }
@@ -1378,9 +1519,18 @@ function Element.new(props)
-- Handle columnGap and rowGap
if props.columnGap then
if type(props.columnGap) == "string" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.columnGap)
if type(props.columnGap) == "string" or isCalc then
local value, unit = Element._Units.parse(props.columnGap)
self.columnGap = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, self.width)
local resolvedColumnGap = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, self.width)
if type(resolvedColumnGap) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "columnGap resolution returned non-number value",
type = type(resolvedColumnGap),
})
resolvedColumnGap = 0
end
self.columnGap = resolvedColumnGap
else
self.columnGap = props.columnGap
end
@@ -1389,9 +1539,18 @@ function Element.new(props)
end
if props.rowGap then
if type(props.rowGap) == "string" then
local isCalc = Element._Calc and Element._Calc.isCalc(props.rowGap)
if type(props.rowGap) == "string" or isCalc then
local value, unit = Element._Units.parse(props.rowGap)
self.rowGap = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, self.height)
local resolvedRowGap = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, self.height)
if type(resolvedRowGap) ~= "number" then
Element._ErrorHandler:warn("Element", "LAY_003", {
issue = "rowGap resolution returned non-number value",
type = type(resolvedRowGap),
})
resolvedRowGap = 0
end
self.rowGap = resolvedRowGap
else
self.rowGap = props.rowGap
end

View File

@@ -34,24 +34,24 @@ local AnimationProps = {}
---@class ElementProps
---@field id string? -- Unique identifier for the element (auto-generated in immediate mode if not provided)
---@field parent Element? -- Parent element for hierarchical structure
---@field x number|string? -- X coordinate of the element (default: 0)
---@field y number|string? -- Y coordinate of the element (default: 0)
---@field x number|string|CalcObject? -- X coordinate: number (px), string ("50%", "10vw"), or CalcObject from FlexLove.calc() (default: 0)
---@field y number|string|CalcObject? -- Y coordinate: number (px), string ("50%", "10vh"), or CalcObject from FlexLove.calc() (default: 0)
---@field z number? -- Z-index for layering (default: 0)
---@field width number|string? -- Width of the element (default: calculated automatically)
---@field height number|string? -- Height of the element (default: calculated automatically)
---@field top number|string? -- Offset from top edge (CSS-style positioning)
---@field right number|string? -- Offset from right edge (CSS-style positioning)
---@field bottom number|string? -- Offset from bottom edge (CSS-style positioning)
---@field left number|string? -- Offset from left edge (CSS-style positioning)
---@field width number|string|CalcObject? -- Width of the element: number (px), string ("50%", "10vw"), or CalcObject from FlexLove.calc() (default: calculated automatically)
---@field height number|string|CalcObject? -- Height of the element: number (px), string ("50%", "10vh"), or CalcObject from FlexLove.calc() (default: calculated automatically)
---@field top number|string|CalcObject? -- Offset from top edge: number (px), string ("50%", "10vh"), or CalcObject (CSS-style positioning)
---@field right number|string|CalcObject? -- Offset from right edge: number (px), string ("50%", "10vw"), or CalcObject (CSS-style positioning)
---@field bottom number|string|CalcObject? -- Offset from bottom edge: number (px), string ("50%", "10vh"), or CalcObject (CSS-style positioning)
---@field left number|string|CalcObject? -- Offset from left edge: number (px), string ("50%", "10vw"), or CalcObject (CSS-style positioning)
---@field border Border? -- Border configuration for the element
---@field borderColor Color? -- Color of the border (default: black)
---@field opacity number? -- Element opacity 0-1 (default: 1)
---@field visibility "visible"|"hidden"? -- Element visibility (default: "visible")
---@field backgroundColor Color? -- Background color (default: transparent)
---@field cornerRadius number|{topLeft:number?, topRight:number?, bottomLeft:number?, bottomRight:number?}? -- Corner radius: number (all corners) or table for individual corners (default: 0)
---@field gap number|string? -- Space between children elements (default: 0)
---@field padding number|string|{top:number|string?, right:number|string?, bottom:number|string?, left:number|string?, horizontal:number|string?, vertical:number|string?}? -- Padding around children: single value for all sides or table for individual sides (default: {top=0, right=0, bottom=0, left=0})
---@field margin number|string|{top:number|string?, right:number|string?, bottom:number|string?, left:number|string?, horizontal:number|string?, vertical:number|string?}? -- Margin around element: single value for all sides or table for individual sides (default: {top=0, right=0, bottom=0, left=0})
---@field gap number|string|CalcObject? -- Space between children elements: number (px), string ("50%", "10vw"), or CalcObject from FlexLove.calc() (default: 0)
---@field padding number|string|CalcObject|{top:number|string|CalcObject?, right:number|string|CalcObject?, bottom:number|string|CalcObject?, left:number|string|CalcObject?, horizontal:number|string|CalcObject?, vertical:number|string|CalcObject?}? -- Padding around children: single value, string, CalcObject for all sides, or table for individual sides (default: {top=0, right=0, bottom=0, left=0})
---@field margin number|string|CalcObject|{top:number|string|CalcObject?, right:number|string|CalcObject?, bottom:number|string|CalcObject?, left:number|string|CalcObject?, horizontal:number|string|CalcObject?, vertical:number|string|CalcObject?}? -- Margin around element: single value, string, CalcObject for all sides, or table for individual sides (default: {top=0, right=0, bottom=0, left=0})
---@field text string? -- Text content to display (default: nil)
---@field textAlign TextAlign? -- Alignment of the text content (default: START)
---@field textColor Color? -- Color of the text content (default: black or theme text color)
@@ -84,8 +84,8 @@ local AnimationProps = {}
---@field transition TransitionProps? -- Transition settings for animations
---@field gridRows number? -- Number of rows in the grid (default: 1)
---@field gridColumns number? -- Number of columns in the grid (default: 1)
---@field columnGap number|string? -- Gap between grid columns (default: 0)
---@field rowGap number|string? -- Gap between grid rows (default: 0)
---@field columnGap number|string|CalcObject? -- Gap between grid columns: number (px), string ("50%", "10vw"), or CalcObject from FlexLove.calc() (default: 0)
---@field rowGap number|string|CalcObject? -- Gap between grid rows: number (px), string ("50%", "10vh"), or CalcObject from FlexLove.calc() (default: 0)
---@field theme string? -- Theme name to use (e.g., "space", "metal"). Defaults to theme from flexlove.init()
---@field themeComponent string? -- Theme component to use (e.g., "panel", "button", "input"). If nil, no theme is applied
---@field disabled boolean? -- Whether the element is disabled (default: false)
@@ -211,3 +211,74 @@ local FlexLoveConfig = {}
---@field _backdropBlurQuality number?
---@field _contentBlurRadius number?
---@field _contentBlurQuality number?
--=====================================--
-- For Calc.lua
--=====================================--
---@class CalcDependencies
---@field ErrorHandler ErrorHandler? -- Error handler module
---@class CalcToken
---@field type string -- Token type: "NUMBER", "UNIT", "PLUS", "MINUS", "MULTIPLY", "DIVIDE", "LPAREN", "RPAREN", "EOF"
---@field value number? -- Numeric value (for NUMBER tokens)
---@field unit string? -- Unit type: "px", "%", "vw", "vh", "ew", "eh" (for NUMBER tokens)
---@class CalcASTNode
---@field type string -- Node type: "number", "add", "subtract", "multiply", "divide"
---@field value number? -- Numeric value (for "number" nodes)
---@field unit string? -- Unit type (for "number" nodes)
---@field left CalcASTNode? -- Left operand (for operator nodes)
---@field right CalcASTNode? -- Right operand (for operator nodes)
---@class CalcObject
---@field _isCalc boolean -- Marker to identify calc objects (always true)
---@field _expr string -- Original expression string
---@field _ast CalcASTNode? -- Parsed abstract syntax tree (nil if parsing failed)
---@field _error string? -- Error message if parsing failed
--=====================================--
-- For FlexLove.lua Internals
--=====================================--
---@class GCConfig
---@field strategy string -- "auto", "periodic", "manual", or "disabled"
---@field memoryThreshold number -- MB before forcing GC
---@field interval number -- Frames between GC steps (for periodic mode)
---@field stepSize number -- Work units per GC step (higher = more aggressive)
---@class GCState
---@field framesSinceLastGC number -- Frames elapsed since last GC
---@field lastMemory number -- Last recorded memory usage in MB
---@field gcCount number -- Total number of GC operations performed
---@class GCStats
---@field gcCount number -- Total number of GC operations performed
---@field framesSinceLastGC number -- Frames elapsed since last GC
---@field currentMemoryMB number -- Current memory usage in MB
---@field strategy string -- Current GC strategy
---@field threshold number -- Memory threshold in MB
---@class FlexLoveDependencies
---@field Context table -- Context module
---@field Theme Theme? -- Theme module
---@field Color Color -- Color module
---@field Calc Calc -- Calc module
---@field Units table -- Units module
---@field Blur table? -- Blur module
---@field ImageRenderer table? -- ImageRenderer module
---@field ImageScaler table? -- ImageScaler module
---@field NinePatch table? -- NinePatch module
---@field RoundedRect table -- RoundedRect module
---@field ImageCache table? -- ImageCache module
---@field utils table -- Utils module
---@field Grid table -- Grid module
---@field InputEvent table -- InputEvent module
---@field GestureRecognizer table? -- GestureRecognizer module
---@field StateManager StateManager -- StateManager module
---@field TextEditor table -- TextEditor module
---@field LayoutEngine LayoutEngine -- LayoutEngine module
---@field Renderer table -- Renderer module
---@field EventHandler EventHandler -- EventHandler module
---@field ScrollManager table -- ScrollManager module
---@field ErrorHandler ErrorHandler -- ErrorHandler module
---@field Performance Performance? -- Performance module
---@field Transform table? -- Transform module