docs improvement

This commit is contained in:
Michael Freno
2025-11-18 13:44:00 -05:00
parent 96150e5cf4
commit d86f7dbd5e
16 changed files with 392 additions and 258 deletions

2
.gitignore vendored
View File

@@ -6,7 +6,7 @@ themes/metal/
themes/space/ themes/space/
.DS_STORE .DS_STORE
tasks tasks
testoutput testoutput*
luacov.* luacov.*
docs/doc.json docs/doc.json
docs/doc.md docs/doc.md

View File

@@ -147,7 +147,8 @@ flexlove._gcState = {
-- Deferred callback queue for operations that cannot run while Canvas is active -- Deferred callback queue for operations that cannot run while Canvas is active
flexlove._deferredCallbacks = {} flexlove._deferredCallbacks = {}
--- Initialize FlexLove with configuration options, set refence scale for autoscaling on window resize, immediate mode, and error logging / error file path --- Set up FlexLove for your application's specific needs - configure responsive scaling, theming, rendering mode, and debugging tools
--- Use this to establish a consistent UI foundation that adapts to different screen sizes and provides performance insights
---@param config {baseScale?: {width?:number, height?:number}, theme?: string|ThemeDefinition, immediateMode?: boolean, stateRetentionFrames?: number, maxStateEntries?: number, autoFrameManagement?: boolean, errorLogFile?: string, enableErrorLogging?: boolean, performanceMonitoring?: boolean, performanceWarnings?: boolean, performanceHudKey?: string, performanceHudPosition?: {x: number, y: number} } ---@param config {baseScale?: {width?:number, height?:number}, theme?: string|ThemeDefinition, immediateMode?: boolean, stateRetentionFrames?: number, maxStateEntries?: number, autoFrameManagement?: boolean, errorLogFile?: string, enableErrorLogging?: boolean, performanceMonitoring?: boolean, performanceWarnings?: boolean, performanceHudKey?: string, performanceHudPosition?: {x: number, y: number} }
function flexlove.init(config) function flexlove.init(config)
config = config or {} config = config or {}
@@ -254,8 +255,8 @@ function flexlove.init(config)
end end
end end
--- Queue a callback to be executed after the current frame's canvas operations complete --- Safely schedule operations that modify LÖVE's rendering state (like window mode changes) to execute after all canvas operations complete
--- This is necessary for operations that cannot run while a Canvas is active (e.g., love.window.setMode) --- Prevents crashes from attempting canvas-incompatible operations during rendering
---@param callback function The callback to execute ---@param callback function The callback to execute
function flexlove.deferCallback(callback) function flexlove.deferCallback(callback)
if type(callback) ~= "function" then if type(callback) ~= "function" then
@@ -265,9 +266,8 @@ function flexlove.deferCallback(callback)
table.insert(flexlove._deferredCallbacks, callback) table.insert(flexlove._deferredCallbacks, callback)
end end
--- Execute all deferred callbacks and clear the queue --- Execute deferred operations at the safest point in the render cycle - after all canvas operations are complete
--- IMPORTANT: This MUST be called at the very end of love.draw() after ALL canvases --- Call this at the end of love.draw() to enable window resizing and other state-modifying operations without crashes
--- have been released, including any canvases created by the application (not just FlexLove's canvases)
--- @usage --- @usage
--- function love.draw() --- function love.draw()
--- love.graphics.setCanvas(myCanvas) --- love.graphics.setCanvas(myCanvas)
@@ -293,6 +293,8 @@ function flexlove.executeDeferredCallbacks()
end end
end end
--- Recalculate all UI layouts when the window size changes - ensures your interface adapts seamlessly to new dimensions
--- Hook this to love.resize() to maintain proper scaling and positioning across window size changes
function flexlove.resize() function flexlove.resize()
local newWidth, newHeight = love.window.getMode() local newWidth, newHeight = love.window.getMode()
@@ -320,7 +322,8 @@ function flexlove.resize()
end end
end end
--- Can also be set in init() --- Switch between immediate mode (React-like, recreates UI each frame) and retained mode (persistent elements) to match your architectural needs
--- Use immediate for simpler state management and declarative UIs, retained for performance-critical applications with complex state
---@param mode "immediate"|"retained" ---@param mode "immediate"|"retained"
function flexlove.setMode(mode) function flexlove.setMode(mode)
if mode == "immediate" then if mode == "immediate" then
@@ -340,13 +343,15 @@ function flexlove.setMode(mode)
end end
end end
--- Check which rendering mode is active to conditionally handle state management logic
--- Useful for libraries and reusable components that need to adapt to different rendering strategies
---@return "immediate"|"retained" ---@return "immediate"|"retained"
function flexlove.getMode() function flexlove.getMode()
return flexlove._immediateMode and "immediate" or "retained" return flexlove._immediateMode and "immediate" or "retained"
end end
--- Begin a new immediate mode frame --- Manually start a new frame in immediate mode for precise control over the UI lifecycle
--- You do NOT need to call this directly, it will autodetect, but you can if you need more granular control - must then paired with endFrame() --- Only needed when you want explicit frame boundaries; otherwise FlexLove auto-manages frames
function flexlove.beginFrame() function flexlove.beginFrame()
if not flexlove._immediateMode then if not flexlove._immediateMode then
return return
@@ -364,9 +369,8 @@ function flexlove.beginFrame()
Context.clearFrameElements() Context.clearFrameElements()
end end
--- End the current immediate mode frame --- Finalize the frame in immediate mode, triggering layout calculations and state persistence
--- You do NOT need to call this directly unless you call beginFrame() manually - it will autodetect, but you can if you need more granular control --- Only needed when manually controlling frames with beginFrame(); otherwise handled automatically
--- MUST BE PAIRED WITH beginFrame()
function flexlove.endFrame() function flexlove.endFrame()
if not flexlove._immediateMode then if not flexlove._immediateMode then
return return
@@ -436,6 +440,8 @@ flexlove._gameCanvas = nil
flexlove._backdropCanvas = nil flexlove._backdropCanvas = nil
flexlove._canvasDimensions = { width = 0, height = 0 } flexlove._canvasDimensions = { width = 0, height = 0 }
--- Render all UI elements with optional backdrop blur support for glassmorphic effects
--- Place your game scene in gameDrawFunc to enable backdrop blur on UI elements; use postDrawFunc for overlays
---@param gameDrawFunc function|nil pass component draws that should be affected by a backdrop blur ---@param gameDrawFunc function|nil pass component draws that should be affected by a backdrop blur
---@param postDrawFunc function|nil pass component draws that should NOT be affected by a backdrop blur ---@param postDrawFunc function|nil pass component draws that should NOT be affected by a backdrop blur
function flexlove.draw(gameDrawFunc, postDrawFunc) function flexlove.draw(gameDrawFunc, postDrawFunc)
@@ -566,7 +572,8 @@ local function isAncestor(element, target)
return false return false
end end
--- Find the topmost element at given coordinates --- Determine which UI element the user is interacting with at a specific screen position
--- Essential for custom input handling, tooltips, or debugging click targets in complex layouts
---@param x number ---@param x number
---@param y number ---@param y number
---@return Element? ---@return Element?
@@ -662,6 +669,8 @@ function flexlove.getElementAtPosition(x, y)
return blockingElements[1] return blockingElements[1]
end end
--- Update all UI animations, interactions, and state changes each frame
--- Hook this to love.update() to enable hover effects, animations, text cursors, and scrolling
---@param dt number ---@param dt number
function flexlove.update(dt) function flexlove.update(dt)
-- Update Performance module with actual delta time for accurate FPS -- Update Performance module with actual delta time for accurate FPS
@@ -742,7 +751,8 @@ function flexlove._manageGC()
-- "manual" strategy: no automatic GC, user must call flexlove.collectGarbage() -- "manual" strategy: no automatic GC, user must call flexlove.collectGarbage()
end end
--- Manual garbage collection control --- Manually trigger garbage collection to prevent frame drops during critical gameplay moments
--- Use this to control when memory cleanup happens rather than letting it occur unpredictably
---@param mode? string "collect" for full GC, "step" for incremental (default: "collect") ---@param mode? string "collect" for full GC, "step" for incremental (default: "collect")
---@param stepSize? number Work units for step mode (default: 200) ---@param stepSize? number Work units for step mode (default: 200)
function flexlove.collectGarbage(mode, stepSize) function flexlove.collectGarbage(mode, stepSize)
@@ -760,7 +770,8 @@ function flexlove.collectGarbage(mode, stepSize)
end end
end end
--- Set GC strategy --- Choose how FlexLove manages memory cleanup to balance performance and memory usage for your app's needs
--- Use "manual" for tight control in performance-critical sections, "auto" for hands-off operation
---@param strategy string "auto", "periodic", "manual", or "disabled" ---@param strategy string "auto", "periodic", "manual", or "disabled"
function flexlove.setGCStrategy(strategy) function flexlove.setGCStrategy(strategy)
if strategy == "auto" or strategy == "periodic" or strategy == "manual" or strategy == "disabled" then if strategy == "auto" or strategy == "periodic" or strategy == "manual" or strategy == "disabled" then
@@ -770,7 +781,8 @@ function flexlove.setGCStrategy(strategy)
end end
end end
--- Get GC statistics --- Monitor memory management behavior to diagnose performance issues and tune GC settings
--- Use this to identify memory leaks or optimize garbage collection timing
---@return table stats {gcCount, framesSinceLastGC, currentMemoryMB, strategy} ---@return table stats {gcCount, framesSinceLastGC, currentMemoryMB, strategy}
function flexlove.getGCStats() function flexlove.getGCStats()
return { return {
@@ -782,6 +794,8 @@ function flexlove.getGCStats()
} }
end end
--- Forward text input to focused editable elements like text fields and text areas
--- Hook this to love.textinput() to enable text entry in your UI
---@param text string ---@param text string
function flexlove.textinput(text) function flexlove.textinput(text)
if flexlove._focusedElement then if flexlove._focusedElement then
@@ -789,6 +803,8 @@ function flexlove.textinput(text)
end end
end end
--- Handle keyboard input for text editing, navigation, and performance overlay toggling
--- Hook this to love.keypressed() to enable text selection, cursor movement, and the performance HUD
---@param key string ---@param key string
---@param scancode string ---@param scancode string
---@param isrepeat boolean ---@param isrepeat boolean
@@ -800,6 +816,8 @@ function flexlove.keypressed(key, scancode, isrepeat)
end end
end end
--- Enable mouse wheel scrolling in scrollable containers and lists
--- Hook this to love.wheelmoved() to allow users to scroll through content naturally
---@param dx number ---@param dx number
---@param dy number ---@param dy number
function flexlove.wheelmoved(dx, dy) function flexlove.wheelmoved(dx, dy)
@@ -926,7 +944,8 @@ function flexlove.wheelmoved(dx, dy)
end end
end end
--- destroys all top-level elements and resets the framework state --- Clean up all UI elements and reset FlexLove to initial state when changing scenes or shutting down
--- Use this to prevent memory leaks when transitioning between game states or menus
function flexlove.destroy() function flexlove.destroy()
for _, win in ipairs(flexlove.topElements) do for _, win in ipairs(flexlove.topElements) do
win:destroy() win:destroy()
@@ -951,6 +970,8 @@ function flexlove.destroy()
StateManager:reset() StateManager:reset()
end end
--- Create a new UI element with flexbox layout, styling, and interaction capabilities
--- This is your primary API for building interfaces - buttons, panels, text, images, and containers
---@param props ElementProps ---@param props ElementProps
---@return Element ---@return Element
function flexlove.new(props) function flexlove.new(props)
@@ -1058,6 +1079,8 @@ function flexlove.new(props)
return element return element
end end
--- Check how many UI element states are being tracked in immediate mode to detect memory leaks
--- Use this during development to ensure states are properly cleaned up
---@return number ---@return number
function flexlove.getStateCount() function flexlove.getStateCount()
if not flexlove._immediateMode then if not flexlove._immediateMode then
@@ -1066,7 +1089,8 @@ function flexlove.getStateCount()
return StateManager.getStateCount() return StateManager.getStateCount()
end end
--- Clear state for a specific element ID --- Remove stored state for a specific element when you know it won't be rendered again
--- Use this to immediately free memory for elements you've removed from your UI
---@param id string ---@param id string
function flexlove.clearState(id) function flexlove.clearState(id)
if not flexlove._immediateMode then if not flexlove._immediateMode then
@@ -1075,7 +1099,8 @@ function flexlove.clearState(id)
StateManager.clearState(id) StateManager.clearState(id)
end end
--- Clear all immediate mode states --- Wipe all element state when transitioning between completely different UI screens
--- Use this for scene transitions to start with a clean slate and prevent state pollution
function flexlove.clearAllStates() function flexlove.clearAllStates()
if not flexlove._immediateMode then if not flexlove._immediateMode then
return return
@@ -1083,7 +1108,8 @@ function flexlove.clearAllStates()
StateManager.clearAllStates() StateManager.clearAllStates()
end end
--- Get state (immediate mode) statistics (for debugging) --- Inspect state management metrics to diagnose performance issues and optimize immediate mode usage
--- Use this to understand state lifecycle and identify unexpected state accumulation
---@return { stateCount: number, frameNumber: number, oldestState: number|nil, newestState: number|nil } ---@return { stateCount: number, frameNumber: number, oldestState: number|nil, newestState: number|nil }
function flexlove.getStateStats() function flexlove.getStateStats()
if not flexlove._immediateMode then if not flexlove._immediateMode then

View File

@@ -73,7 +73,8 @@ local Easing = {
local Animation = {} local Animation = {}
Animation.__index = Animation Animation.__index = Animation
---Create a new animation instance --- Build smooth, timed transitions between visual states to create polished, professional UIs
--- Use this to animate position, size, opacity, colors, and other properties with customizable easing
---@param props AnimationProps Animation properties ---@param props AnimationProps Animation properties
---@return Animation animation The new animation instance ---@return Animation animation The new animation instance
function Animation.new(props) function Animation.new(props)
@@ -136,7 +137,8 @@ function Animation.new(props)
return self return self
end end
---Update the animation with delta time --- Advance the animation timeline and calculate interpolated values for the current frame
--- Call this each frame to progress the animation; returns true when complete for cleanup
---@param dt number Delta time in seconds ---@param dt number Delta time in seconds
---@param element table? Optional element reference for callbacks ---@param element table? Optional element reference for callbacks
---@return boolean completed True if animation is complete ---@return boolean completed True if animation is complete
@@ -302,7 +304,8 @@ local function lerpTable(startTable, finalTable, easedT)
return result return result
end end
---Interpolate animation values at current time --- Calculate the current animated values between start and end states based on elapsed time
--- Use this to get the interpolated properties to apply to your element
---@return table result Interpolated values {width?, height?, opacity?, x?, y?, backgroundColor?, ...} ---@return table result Interpolated values {width?, height?, opacity?, x?, y?, backgroundColor?, ...}
function Animation:interpolate() function Animation:interpolate()
-- Return cached result if not dirty (avoids recalculation) -- Return cached result if not dirty (avoids recalculation)
@@ -391,7 +394,8 @@ function Animation:interpolate()
return result return result
end end
---Apply this animation to an element --- Attach this animation to an element so it automatically updates and applies changes
--- Use this for hands-off animation that integrates with FlexLove's rendering system
---@param element Element The element to apply animation to ---@param element Element The element to apply animation to
function Animation:apply(element) function Animation:apply(element)
if not ErrorHandler then if not ErrorHandler then
@@ -417,7 +421,8 @@ function Animation:setTransformModule(TransformModule)
self._Transform = TransformModule self._Transform = TransformModule
end end
---Pause the animation --- Temporarily halt the animation without losing progress
--- Use this to freeze animations during pause menus or cutscenes
function Animation:pause() function Animation:pause()
if self._state == "playing" or self._state == "pending" then if self._state == "playing" or self._state == "pending" then
self._paused = true self._paused = true
@@ -425,7 +430,8 @@ function Animation:pause()
end end
end end
---Resume the animation --- Continue a paused animation from where it left off
--- Use this to unpause animations when returning from pause menus
function Animation:resume() function Animation:resume()
if self._state == "paused" then if self._state == "paused" then
self._paused = false self._paused = false
@@ -433,24 +439,28 @@ function Animation:resume()
end end
end end
---Check if animation is paused --- Query pause state to conditionally handle animation logic
--- Use this to sync UI behavior with animation state
---@return boolean paused ---@return boolean paused
function Animation:isPaused() function Animation:isPaused()
return self._paused return self._paused
end end
---Reverse the animation direction --- Flip the animation to play backwards, creating smooth transitions in both directions
--- Use this for hover effects that reverse on mouse-out or toggleable UI elements
function Animation:reverse() function Animation:reverse()
self._reversed = not self._reversed self._reversed = not self._reversed
end end
---Check if animation is reversed --- Determine current playback direction for conditional animation logic
--- Use this to track which direction the animation is playing
---@return boolean reversed ---@return boolean reversed
function Animation:isReversed() function Animation:isReversed()
return self._reversed return self._reversed
end end
---Set animation playback speed --- Control animation tempo for slow-motion or fast-forward effects
--- Use this for bullet-time, game speed multipliers, or debugging
---@param speed number Speed multiplier (1.0 = normal, 2.0 = double speed, 0.5 = half speed) ---@param speed number Speed multiplier (1.0 = normal, 2.0 = double speed, 0.5 = half speed)
function Animation:setSpeed(speed) function Animation:setSpeed(speed)
if type(speed) == "number" and speed > 0 then if type(speed) == "number" and speed > 0 then
@@ -458,13 +468,15 @@ function Animation:setSpeed(speed)
end end
end end
---Get animation playback speed --- Check current playback speed for debugging or UI display
--- Use this to show animation speed in dev tools
---@return number speed Current speed multiplier ---@return number speed Current speed multiplier
function Animation:getSpeed() function Animation:getSpeed()
return self._speed return self._speed
end end
---Seek to a specific time in the animation --- Jump to any point in the animation timeline for previewing or state restoration
--- Use this to skip ahead, rewind, or restore saved animation states
---@param time number Time in seconds (clamped to 0-duration) ---@param time number Time in seconds (clamped to 0-duration)
function Animation:seek(time) function Animation:seek(time)
if type(time) == "number" then if type(time) == "number" then
@@ -473,13 +485,15 @@ function Animation:seek(time)
end end
end end
---Get current animation state --- Query animation lifecycle state for conditional logic and debugging
--- Use this to determine if cleanup is needed or to prevent duplicate animations
---@return string state Current state: "pending", "playing", "paused", "completed", "cancelled" ---@return string state Current state: "pending", "playing", "paused", "completed", "cancelled"
function Animation:getState() function Animation:getState()
return self._state return self._state
end end
---Cancel the animation --- Stop the animation immediately without completing, triggering the onCancel callback
--- Use this to abort animations when UI elements are removed or user cancels an action
---@param element table? Optional element reference for callback ---@param element table? Optional element reference for callback
function Animation:cancel(element) function Animation:cancel(element)
if self._state ~= "cancelled" and self._state ~= "completed" then if self._state ~= "cancelled" and self._state ~= "completed" then
@@ -493,7 +507,8 @@ function Animation:cancel(element)
end end
end end
---Reset the animation to its initial state --- Return the animation to the beginning for replay
--- Use this to reuse animation instances without recreating them
function Animation:reset() function Animation:reset()
self.elapsed = 0 self.elapsed = 0
self._hasStarted = false self._hasStarted = false
@@ -502,13 +517,15 @@ function Animation:reset()
self._resultDirty = true self._resultDirty = true
end end
---Get the current progress of the animation --- Get normalized animation progress for progress bars or synchronized effects
--- Use this to drive secondary animations or display completion percentage
---@return number progress Progress from 0 to 1 ---@return number progress Progress from 0 to 1
function Animation:getProgress() function Animation:getProgress()
return math.min(self.elapsed / self.duration, 1) return math.min(self.elapsed / self.duration, 1)
end end
---Chain another animation after this one completes --- Create sequential animation flows that play one after another
--- Use this to build complex multi-step animations like slide-in-then-fade
---@param nextAnimation Animation|function Animation instance or factory function that returns an animation ---@param nextAnimation Animation|function Animation instance or factory function that returns an animation
---@return Animation nextAnimation The chained animation (for further chaining) ---@return Animation nextAnimation The chained animation (for further chaining)
function Animation:chain(nextAnimation) function Animation:chain(nextAnimation)
@@ -528,7 +545,8 @@ function Animation:chain(nextAnimation)
end end
end end
---Add delay before animation starts --- Introduce a wait period before animation begins for staggered effects
--- Use this to create cascading animations or timed sequences
---@param seconds number Delay duration in seconds ---@param seconds number Delay duration in seconds
---@return Animation self For chaining ---@return Animation self For chaining
function Animation:delay(seconds) function Animation:delay(seconds)
@@ -545,7 +563,8 @@ function Animation:delay(seconds)
return self return self
end end
---Repeat animation multiple times --- Loop the animation for pulsing effects, loading indicators, or continuous motion
--- Use this for idle animations and attention-grabbing elements
---@param count number Number of times to repeat (0 = infinite loop) ---@param count number Number of times to repeat (0 = infinite loop)
---@return Animation self For chaining ---@return Animation self For chaining
function Animation:repeatCount(count) function Animation:repeatCount(count)
@@ -562,7 +581,8 @@ function Animation:repeatCount(count)
return self return self
end end
---Enable yoyo mode (animation reverses direction on each repeat) --- Make repeating animations play forwards then backwards for smooth oscillation
--- Use this for breathing effects, pulsing highlights, or pendulum motions
---@param enabled boolean? Enable yoyo mode (default: true) ---@param enabled boolean? Enable yoyo mode (default: true)
---@return Animation self For chaining ---@return Animation self For chaining
function Animation:yoyo(enabled) function Animation:yoyo(enabled)
@@ -573,7 +593,8 @@ function Animation:yoyo(enabled)
return self return self
end end
--- Create a simple fade animation --- Quickly create fade in/out effects without manually specifying start/end states
--- Use this convenience method for common opacity transitions in tooltips, notifications, and overlays
---@param duration number Duration in seconds ---@param duration number Duration in seconds
---@param fromOpacity number Starting opacity (0-1) ---@param fromOpacity number Starting opacity (0-1)
---@param toOpacity number Ending opacity (0-1) ---@param toOpacity number Ending opacity (0-1)
@@ -601,7 +622,8 @@ function Animation.fade(duration, fromOpacity, toOpacity, easing)
}) })
end end
--- Create a simple scale animation --- Quickly create grow/shrink effects without manually specifying dimensions
--- Use this convenience method for bounce effects, pop-ups, and attention animations
---@param duration number Duration in seconds ---@param duration number Duration in seconds
---@param fromScale {width:number,height:number} Starting scale ---@param fromScale {width:number,height:number} Starting scale
---@param toScale {width:number,height:number} Ending scale ---@param toScale {width:number,height:number} Ending scale

View File

@@ -13,7 +13,8 @@ local ErrorHandler = nil
---@field onComplete function? Called when all animations complete: (group) ---@field onComplete function? Called when all animations complete: (group)
---@field onStart function? Called when group starts: (group) ---@field onStart function? Called when group starts: (group)
--- Create a new animation group --- Coordinate multiple animations to play together, in sequence, or staggered for complex choreographed effects
--- Use this to synchronize related UI changes like simultaneous fades or sequential reveals
---@param props AnimationGroupProps ---@param props AnimationGroupProps
---@return AnimationGroup group ---@return AnimationGroup group
function AnimationGroup.new(props) function AnimationGroup.new(props)
@@ -142,7 +143,8 @@ function AnimationGroup:_updateStagger(dt, element)
return allFinished return allFinished
end end
--- Update the animation group --- Advance all animations in the group according to their coordination mode
--- Call this each frame to progress parallel, sequential, or staggered animations
---@param dt number Delta time ---@param dt number Delta time
---@param element table? Optional element reference for callbacks ---@param element table? Optional element reference for callbacks
---@return boolean finished True if group is complete ---@return boolean finished True if group is complete
@@ -191,7 +193,8 @@ function AnimationGroup:update(dt, element)
return finished return finished
end end
--- Pause all animations in the group --- Freeze the entire animation sequence in unison
--- Use this to pause complex multi-part animations during game pauses
function AnimationGroup:pause() function AnimationGroup:pause()
self._paused = true self._paused = true
for _, anim in ipairs(self.animations) do for _, anim in ipairs(self.animations) do
@@ -201,7 +204,8 @@ function AnimationGroup:pause()
end end
end end
--- Resume all animations in the group --- Continue all paused animations simultaneously from their paused states
--- Use this to unpause coordinated animation sequences
function AnimationGroup:resume() function AnimationGroup:resume()
self._paused = false self._paused = false
for _, anim in ipairs(self.animations) do for _, anim in ipairs(self.animations) do
@@ -211,13 +215,15 @@ function AnimationGroup:resume()
end end
end end
--- Check if group is paused --- Determine if the entire group is currently paused
--- Use this to sync other game logic with animation group state
---@return boolean paused ---@return boolean paused
function AnimationGroup:isPaused() function AnimationGroup:isPaused()
return self._paused return self._paused
end end
--- Reverse all animations in the group --- Flip all animations to play backwards together
--- Use this to reverse complex transitions like panel opens/closes
function AnimationGroup:reverse() function AnimationGroup:reverse()
for _, anim in ipairs(self.animations) do for _, anim in ipairs(self.animations) do
if type(anim.reverse) == "function" then if type(anim.reverse) == "function" then
@@ -226,7 +232,8 @@ function AnimationGroup:reverse()
end end
end end
--- Set speed for all animations in the group --- Control the tempo of all animations simultaneously
--- Use this for slow-motion effects or debugging without adjusting individual animations
---@param speed number Speed multiplier ---@param speed number Speed multiplier
function AnimationGroup:setSpeed(speed) function AnimationGroup:setSpeed(speed)
for _, anim in ipairs(self.animations) do for _, anim in ipairs(self.animations) do
@@ -236,7 +243,8 @@ function AnimationGroup:setSpeed(speed)
end end
end end
--- Cancel all animations in the group --- Abort all animations in the group immediately without completion
--- Use this when UI is dismissed mid-animation or transitions are interrupted
---@param element table? Optional element reference for callbacks ---@param element table? Optional element reference for callbacks
function AnimationGroup:cancel(element) function AnimationGroup:cancel(element)
if self._state ~= "cancelled" and self._state ~= "completed" then if self._state ~= "cancelled" and self._state ~= "completed" then
@@ -249,7 +257,8 @@ function AnimationGroup:cancel(element)
end end
end end
--- Reset the animation group to initial state --- Restart the entire group from the beginning for reuse
--- Use this to replay animation sequences without recreating objects
function AnimationGroup:reset() function AnimationGroup:reset()
self._currentIndex = 1 self._currentIndex = 1
self._staggerElapsed = 0 self._staggerElapsed = 0
@@ -265,13 +274,15 @@ function AnimationGroup:reset()
end end
end end
--- Get the current state of the group --- Check the overall lifecycle state of the animation group
--- Use this to conditionally trigger follow-up actions or cleanup
---@return string state "ready", "playing", "completed", "cancelled" ---@return string state "ready", "playing", "completed", "cancelled"
function AnimationGroup:getState() function AnimationGroup:getState()
return self._state return self._state
end end
--- Get the overall progress of the group (0-1) --- Calculate completion percentage across all animations in the group
--- Use this for progress bars or to synchronize other effects with the group
---@return number progress ---@return number progress
function AnimationGroup:getProgress() function AnimationGroup:getProgress()
if #self.animations == 0 then if #self.animations == 0 then
@@ -305,7 +316,8 @@ function AnimationGroup:getProgress()
end end
end end
--- Apply this animation group to an element --- Attach this group to an element for automatic updates and integration
--- Use this for hands-off animation management within FlexLove's system
---@param element Element The element to apply animations to ---@param element Element The element to apply animations to
function AnimationGroup:apply(element) function AnimationGroup:apply(element)
if not element or type(element) ~= "table" then if not element or type(element) ~= "table" then

View File

@@ -53,7 +53,8 @@ local NAMED_COLORS = {
local Color = {} local Color = {}
Color.__index = Color Color.__index = Color
--- Create a new color instance --- Build type-safe color objects with automatic validation and clamping
--- Use this to avoid invalid color values and ensure consistent LÖVE-compatible colors (0-1 range)
---@param r number? Red component (0-1), defaults to 0 ---@param r number? Red component (0-1), defaults to 0
---@param g number? Green component (0-1), defaults to 0 ---@param g number? Green component (0-1), defaults to 0
---@param b number? Blue component (0-1), defaults to 0 ---@param b number? Blue component (0-1), defaults to 0
@@ -75,7 +76,8 @@ function Color.new(r, g, b, a)
return self return self
end end
---Convert color to RGBA components --- Extract individual color channels for use with love.graphics.setColor()
--- Use this to pass colors to LÖVE's rendering functions
---@return number r Red component (0-1) ---@return number r Red component (0-1)
---@return number g Green component (0-1) ---@return number g Green component (0-1)
---@return number b Blue component (0-1) ---@return number b Blue component (0-1)
@@ -84,8 +86,8 @@ function Color:toRGBA()
return self.r, self.g, self.b, self.a return self.r, self.g, self.b, self.a
end end
--- Convert hex string to color --- Parse CSS-style hex colors into Color objects for designer-friendly workflows
--- Supports both 6-digit (#RRGGBB) and 8-digit (#RRGGBBAA) hex formats --- Use this to work with colors from design tools that export hex values
---@param hexWithTag string Hex color string (e.g. "#RRGGBB" or "#RRGGBBAA") ---@param hexWithTag string Hex color string (e.g. "#RRGGBB" or "#RRGGBBAA")
---@return Color color The parsed color (returns white on error with warning) ---@return Color color The parsed color (returns white on error with warning)
function Color.fromHex(hexWithTag) function Color.fromHex(hexWithTag)
@@ -146,7 +148,8 @@ function Color.fromHex(hexWithTag)
end end
end end
--- Validate a single color channel value --- Verify and sanitize individual color components to prevent rendering errors
--- Use this to safely process user input or external color data
---@param value any Value to validate ---@param value any Value to validate
---@param max number? Maximum value (255 for 0-255 range, 1 for 0-1 range), defaults to 1 ---@param max number? Maximum value (255 for 0-255 range, 1 for 0-1 range), defaults to 1
---@return boolean valid True if valid ---@return boolean valid True if valid
@@ -307,7 +310,8 @@ function Color.isValidColorFormat(value)
return nil return nil
end end
--- Validate a color value --- Check if a color value is usable before processing to provide clear error messages
--- Use this for config validation and debugging malformed color data
---@param value any Color value to validate ---@param value any Color value to validate
---@param options table? Validation options {allowNamed: boolean, requireAlpha: boolean} ---@param options table? Validation options {allowNamed: boolean, requireAlpha: boolean}
---@return boolean valid True if valid ---@return boolean valid True if valid
@@ -342,7 +346,8 @@ function Color.validateColor(value, options)
return true, nil return true, nil
end end
--- Sanitize a color value (always returns a valid Color) --- Convert any color format to a valid Color object with graceful fallbacks
--- Use this to robustly handle colors from any source without crashes
---@param value any Color value to sanitize (hex, named, table, or Color instance) ---@param value any Color value to sanitize (hex, named, table, or Color instance)
---@param default Color? Default color if invalid (defaults to black) ---@param default Color? Default color if invalid (defaults to black)
---@return Color color Sanitized color instance (guaranteed non-nil) ---@return Color color Sanitized color instance (guaranteed non-nil)
@@ -418,14 +423,16 @@ function Color.sanitizeColor(value, default)
return default return default
end end
--- Parse a color from various formats (always returns a valid Color) --- Universally convert any color format (hex, named, table) into a Color object
--- Use this as your main color input handler to accept flexible color specifications
---@param value any Color value (hex string, named color, table, or Color instance) ---@param value any Color value (hex string, named color, table, or Color instance)
---@return Color color Parsed color instance (defaults to black on error) ---@return Color color Parsed color instance (defaults to black on error)
function Color.parse(value) function Color.parse(value)
return Color.sanitizeColor(value, Color.new(0, 0, 0, 1)) return Color.sanitizeColor(value, Color.new(0, 0, 0, 1))
end end
--- Linear interpolation between two colors --- Smoothly transition between two colors for animations and gradients
--- Use this to create color-based animations without manual channel calculations
---@param colorA Color Starting color ---@param colorA Color Starting color
---@param colorB Color Ending color ---@param colorB Color Ending color
---@param t number Interpolation factor (0-1) ---@param t number Interpolation factor (0-1)

View File

@@ -1415,13 +1415,15 @@ function Element.new(props, deps)
return self return self
end end
--- Get element bounds (content box) --- Retrieve the element's screen-space rectangle for collision detection and positioning calculations
--- Use this for custom layout logic, tooltips, or detecting overlaps between elements
---@return { x:number, y:number, width:number, height:number } ---@return { x:number, y:number, width:number, height:number }
function Element:getBounds() 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 --- Test if a screen coordinate falls within the element's clickable area
--- Use this for custom hit detection or determining which element the mouse is over
--- @param x number --- @param x number
--- @param y number --- @param y number
--- @return boolean --- @return boolean
@@ -1430,13 +1432,15 @@ function Element:contains(x, y)
return bounds.x <= x and bounds.y <= y and bounds.x + bounds.width >= x and bounds.y + bounds.height >= y return bounds.x <= x and bounds.y <= y and bounds.x + bounds.width >= x and bounds.y + bounds.height >= y
end end
--- Get border-box width (including padding) --- Get the element's total width including padding for layout calculations
--- Use this when you need the full visual width rather than just content width
---@return number ---@return number
function Element:getBorderBoxWidth() function Element:getBorderBoxWidth()
return self._borderBoxWidth or (self.width + self.padding.left + self.padding.right) return self._borderBoxWidth or (self.width + self.padding.left + self.padding.right)
end end
--- Get border-box height (including padding) --- Get the element's total height including padding for layout calculations
--- Use this when you need the full visual height rather than just content height
---@return number ---@return number
function Element:getBorderBoxHeight() function Element:getBorderBoxHeight()
return self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom) return self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom)
@@ -1473,7 +1477,8 @@ function Element:_detectOverflow()
end end
end end
--- Set scroll position with bounds clamping (delegates to ScrollManager) --- Programmatically scroll content to any position for implementing "scroll to top" buttons or navigation anchors
--- Use this to create custom scrolling controls or jump to specific content sections
---@param x number? -- X scroll position (nil to keep current) ---@param x number? -- X scroll position (nil to keep current)
---@param y number? -- Y scroll position (nil to keep current) ---@param y number? -- Y scroll position (nil to keep current)
function Element:setScrollPosition(x, y) function Element:setScrollPosition(x, y)
@@ -1561,7 +1566,8 @@ function Element:_handleWheelScroll(x, y)
return false return false
end end
--- Get current scroll position (delegates to ScrollManager) --- Query how far content is scrolled to implement scroll-aware UI like "back to top" buttons
--- Use this to create scroll position indicators or trigger lazy-loading
---@return number scrollX, number scrollY ---@return number scrollX, number scrollY
function Element:getScrollPosition() function Element:getScrollPosition()
if self._scrollManager then if self._scrollManager then
@@ -1570,7 +1576,8 @@ function Element:getScrollPosition()
return 0, 0 return 0, 0
end end
--- Get maximum scroll bounds (delegates to ScrollManager) --- Find the scroll limits for validation and scroll position clamping
--- Use this to determine if content is fully scrolled or calculate remaining scroll distance
---@return number maxScrollX, number maxScrollY ---@return number maxScrollX, number maxScrollY
function Element:getMaxScroll() function Element:getMaxScroll()
if self._scrollManager then if self._scrollManager then
@@ -1579,7 +1586,8 @@ function Element:getMaxScroll()
return 0, 0 return 0, 0
end end
--- Get scroll percentage (0-1) (delegates to ScrollManager) --- Get normalized scroll progress for scroll-based animations or position indicators
--- Use this to drive progress bars or parallax effects based on scroll position
---@return number percentX, number percentY ---@return number percentX, number percentY
function Element:getScrollPercentage() function Element:getScrollPercentage()
if self._scrollManager then if self._scrollManager then
@@ -1588,7 +1596,8 @@ function Element:getScrollPercentage()
return 0, 0 return 0, 0
end end
--- Check if element has overflow (delegates to ScrollManager) --- Determine if content extends beyond visible bounds to conditionally show scrollbars or overflow indicators
--- Use this to decide whether to display scroll hints or enable scroll interactions
---@return boolean hasOverflowX, boolean hasOverflowY ---@return boolean hasOverflowX, boolean hasOverflowY
function Element:hasOverflow() function Element:hasOverflow()
if self._scrollManager then if self._scrollManager then
@@ -1597,7 +1606,8 @@ function Element:hasOverflow()
return false, false return false, false
end end
--- Get content dimensions (including overflow) (delegates to ScrollManager) --- Measure total content size including overflowed areas for scroll calculations
--- Use this to understand how much content exists beyond the visible viewport
---@return number contentWidth, number contentHeight ---@return number contentWidth, number contentHeight
function Element:getContentSize() function Element:getContentSize()
if self._scrollManager then if self._scrollManager then
@@ -1606,7 +1616,8 @@ function Element:getContentSize()
return 0, 0 return 0, 0
end end
--- Scroll by delta amount (delegates to ScrollManager) --- Scroll content by a relative amount for smooth scrolling animations or gesture-based scrolling
--- Use this to implement custom scroll controls or smooth scroll transitions
---@param dx number? -- X delta (nil for no change) ---@param dx number? -- X delta (nil for no change)
---@param dy number? -- Y delta (nil for no change) ---@param dy number? -- Y delta (nil for no change)
function Element:scrollBy(dx, dy) function Element:scrollBy(dx, dy)
@@ -1616,7 +1627,8 @@ function Element:scrollBy(dx, dy)
end end
end end
--- Scroll to top --- Jump to the beginning of scrollable content instantly
--- Use this for "back to top" buttons or resetting scroll position
function Element:scrollToTop() function Element:scrollToTop()
self:setScrollPosition(nil, 0) self:setScrollPosition(nil, 0)
end end
@@ -1634,7 +1646,8 @@ function Element:scrollToLeft()
self:setScrollPosition(0, nil) self:setScrollPosition(0, nil)
end end
--- Scroll to right --- Jump to the rightmost position of horizontally scrollable content
--- Use this to navigate to the end of horizontal lists or carousels
function Element:scrollToRight() function Element:scrollToRight()
if self._scrollManager then if self._scrollManager then
local maxScrollX, _ = self._scrollManager:getMaxScroll() local maxScrollX, _ = self._scrollManager:getMaxScroll()
@@ -1718,7 +1731,8 @@ function Element:getAvailableContentHeight()
return math.max(0, availableHeight) return math.max(0, availableHeight)
end end
--- Add child to element --- Dynamically insert a child element into the hierarchy for runtime UI construction
--- Use this to build interfaces procedurally or add elements based on application state
---@param child Element ---@param child Element
function Element:addChild(child) function Element:addChild(child)
child.parent = self child.parent = self
@@ -1790,7 +1804,8 @@ function Element:addChild(child)
end end
end end
--- Remove a specific child from this element --- Remove a child element from the hierarchy to dynamically update UIs
--- Use this to delete elements when they're no longer needed or respond to user actions
---@param child Element ---@param child Element
function Element:removeChild(child) function Element:removeChild(child)
for i, c in ipairs(self.children) do for i, c in ipairs(self.children) do
@@ -1822,7 +1837,8 @@ function Element:removeChild(child)
end end
end end
--- Remove all children from this element --- Delete all child elements at once for resetting containers or clearing lists
--- Use this to efficiently empty containers when rebuilding UI from scratch
function Element:clearChildren() function Element:clearChildren()
-- Clear parent references for all children -- Clear parent references for all children
for _, child in ipairs(self.children) do for _, child in ipairs(self.children) do
@@ -2661,21 +2677,24 @@ end
-- Input Handling - Focus Management -- Input Handling - Focus Management
-- ==================== -- ====================
--- Focus this element for keyboard input --- Give this element keyboard focus to enable text input or keyboard navigation
--- Use this to automatically focus text fields when showing forms or dialogs
function Element:focus() function Element:focus()
if self._textEditor then if self._textEditor then
self._textEditor:focus() self._textEditor:focus()
end end
end end
--- Remove focus from this element --- Remove keyboard focus to stop capturing input events
--- Use this when closing popups or switching focus to other elements
function Element:blur() function Element:blur()
if self._textEditor then if self._textEditor then
self._textEditor:blur() self._textEditor:blur()
end end
end end
--- Check if this element is focused --- Query focus state to conditionally render focus indicators or handle keyboard input
--- Use this to style focused elements or determine which element receives keyboard events
---@return boolean ---@return boolean
function Element:isFocused() function Element:isFocused()
if self._textEditor then if self._textEditor then
@@ -2688,7 +2707,8 @@ end
-- Input Handling - Text Buffer Management -- Input Handling - Text Buffer Management
-- ==================== -- ====================
--- Get current text buffer --- Retrieve the element's current text content for processing or validation
--- Use this to read user input from text fields or get display text
---@return string ---@return string
function Element:getText() function Element:getText()
if self._textEditor then if self._textEditor then
@@ -2697,7 +2717,8 @@ function Element:getText()
return self.text or "" return self.text or ""
end end
--- Set text buffer and mark dirty --- Update the element's text content programmatically for dynamic labels or resetting inputs
--- Use this to change text without user input, like clearing fields or updating status messages
---@param text string ---@param text string
function Element:setText(text) function Element:setText(text)
if self._textEditor then if self._textEditor then
@@ -2709,7 +2730,8 @@ function Element:setText(text)
self.text = text self.text = text
end end
--- Insert text at position --- Programmatically insert text at any position for autocomplete or text manipulation
--- Use this to implement suggestions, templates, or text snippets
---@param text string -- Text to insert ---@param text string -- Text to insert
---@param position number? -- Position to insert at (default: cursor position) ---@param position number? -- Position to insert at (default: cursor position)
function Element:insertText(text, position) function Element:insertText(text, position)
@@ -2899,7 +2921,8 @@ function Element:_trackActiveAnimations()
end end
end end
--- Set image tint color --- Change the tint color of an image element dynamically for hover effects or state indication
--- Use this to recolor images without replacing the asset, like highlighting selected items
---@param color Color Color to tint the image ---@param color Color Color to tint the image
function Element:setImageTint(color) function Element:setImageTint(color)
self.imageTint = color self.imageTint = color
@@ -2908,7 +2931,8 @@ function Element:setImageTint(color)
end end
end end
--- Set image opacity --- Adjust image transparency independently from the element for fade effects
--- Use this to create image-specific fade animations or disabled states
---@param opacity number Opacity 0-1 ---@param opacity number Opacity 0-1
function Element:setImageOpacity(opacity) function Element:setImageOpacity(opacity)
if opacity ~= nil then if opacity ~= nil then
@@ -2938,7 +2962,8 @@ function Element:setImageRepeat(repeatMode)
end end
end end
--- Rotate element by angle --- Apply rotation transform to create spinning animations or rotated layouts
--- Use this for loading spinners, compass needles, or angled UI elements
---@param angle number Angle in radians ---@param angle number Angle in radians
function Element:rotate(angle) function Element:rotate(angle)
if not self.transform then if not self.transform then
@@ -2947,7 +2972,8 @@ function Element:rotate(angle)
self.transform.rotate = angle self.transform.rotate = angle
end end
--- Scale element --- Resize element visually using scale transforms for zoom effects
--- Use this for hover magnification, shrinking animations, or responsive scaling
---@param scaleX number X-axis scale ---@param scaleX number X-axis scale
---@param scaleY number? Y-axis scale (defaults to scaleX) ---@param scaleY number? Y-axis scale (defaults to scaleX)
function Element:scale(scaleX, scaleY) function Element:scale(scaleX, scaleY)
@@ -2958,7 +2984,8 @@ function Element:scale(scaleX, scaleY)
self.transform.scaleY = scaleY or scaleX self.transform.scaleY = scaleY or scaleX
end end
--- Translate element --- Offset element position using transforms for smooth movement without layout recalculation
--- Use this for parallax effects, draggable elements, or position animations
---@param x number X translation ---@param x number X translation
---@param y number Y translation ---@param y number Y translation
function Element:translate(x, y) function Element:translate(x, y)
@@ -2969,7 +2996,8 @@ function Element:translate(x, y)
self.transform.translateY = y self.transform.translateY = y
end end
--- Set transform origin --- Define the pivot point for rotation and scaling transforms
--- Use this to rotate around corners, edges, or custom points rather than the center
---@param originX number X origin (0-1, where 0.5 is center) ---@param originX number X origin (0-1, where 0.5 is center)
---@param originY number Y origin (0-1, where 0.5 is center) ---@param originY number Y origin (0-1, where 0.5 is center)
function Element:setTransformOrigin(originX, originY) function Element:setTransformOrigin(originX, originY)

View File

@@ -124,7 +124,8 @@ Theme.__index = Theme
local themes = {} local themes = {}
local activeTheme = nil local activeTheme = nil
---Create a new theme instance --- Create reusable design systems with consistent styling, 9-patch assets, and component states
--- Use this to build professional-looking UIs with minimal per-element configuration
---@param definition ThemeDefinition Theme definition table ---@param definition ThemeDefinition Theme definition table
---@return Theme theme The new theme instance ---@return Theme theme The new theme instance
function Theme.new(definition) function Theme.new(definition)
@@ -312,7 +313,8 @@ function Theme.new(definition)
return self return self
end end
--- Load a theme from a Lua file --- Import a theme definition from a file to enable hot-reloading and modular design systems
--- Use this to load bundled or user-created themes dynamically
---@param path string Path to theme definition file (e.g., "space" or "mytheme") ---@param path string Path to theme definition file (e.g., "space" or "mytheme")
---@return Theme? theme The loaded theme, or nil on error ---@return Theme? theme The loaded theme, or nil on error
function Theme.load(path) function Theme.load(path)
@@ -348,7 +350,8 @@ function Theme.load(path)
return theme return theme
end end
---Set the active theme --- Switch the global theme to instantly restyle all themed UI elements
--- Use this to implement light/dark mode toggles or user-selectable skins
---@param themeOrName Theme|string Theme instance or theme name to activate ---@param themeOrName Theme|string Theme instance or theme name to activate
function Theme.setActive(themeOrName) function Theme.setActive(themeOrName)
if type(themeOrName) == "string" then if type(themeOrName) == "string" then
@@ -371,13 +374,15 @@ function Theme.setActive(themeOrName)
end end
end end
--- Get the active theme --- Access the current theme to query colors, fonts, or create theme-aware components
--- Use this to build UI that adapts to the active design system
---@return Theme? theme The active theme, or nil if none is active ---@return Theme? theme The active theme, or nil if none is active
function Theme.getActive() function Theme.getActive()
return activeTheme return activeTheme
end end
--- Get a component from the active theme --- Retrieve pre-configured visual styles for UI components to maintain consistency
--- Use this to apply theme definitions to custom elements
---@param componentName string Name of the component (e.g., "button", "panel") ---@param componentName string Name of the component (e.g., "button", "panel")
---@param state string? Optional state (e.g., "hover", "pressed", "disabled") ---@param state string? Optional state (e.g., "hover", "pressed", "disabled")
---@return ThemeComponent? component Returns component or nil if not found ---@return ThemeComponent? component Returns component or nil if not found
@@ -399,7 +404,8 @@ function Theme.getComponent(componentName, state)
return component return component
end end
--- Get a font from the active theme --- Access theme-defined fonts for consistent typography across your UI
--- Use this to load fonts specified in your theme definition
---@param fontName string Name of the font family (e.g., "default", "heading") ---@param fontName string Name of the font family (e.g., "default", "heading")
---@return string? fontPath Returns font path or nil if not found ---@return string? fontPath Returns font path or nil if not found
function Theme.getFont(fontName) function Theme.getFont(fontName)
@@ -410,7 +416,8 @@ function Theme.getFont(fontName)
return activeTheme.fonts and activeTheme.fonts[fontName] return activeTheme.fonts and activeTheme.fonts[fontName]
end end
--- Get a color from the active theme --- Retrieve semantic colors from the theme palette for consistent brand identity
--- Use this instead of hardcoding colors to support themeing and color scheme switches
---@param colorName string Name of the color (e.g., "primary", "secondary") ---@param colorName string Name of the color (e.g., "primary", "secondary")
---@return Color? color Returns Color instance or nil if not found ---@return Color? color Returns Color instance or nil if not found
function Theme.getColor(colorName) function Theme.getColor(colorName)
@@ -461,7 +468,8 @@ function Theme.getAllColors()
return activeTheme.colors return activeTheme.colors
end end
--- Get a color with a fallback if not found --- Safely get theme colors with guaranteed fallbacks to prevent missing color errors
--- Use this when you need a color value no matter what
---@param colorName string Name of the color to retrieve ---@param colorName string Name of the color to retrieve
---@param fallback Color? Fallback color if not found (default: white) ---@param fallback Color? Fallback color if not found (default: white)
---@return Color color The color or fallback (guaranteed non-nil) ---@return Color color The color or fallback (guaranteed non-nil)
@@ -721,7 +729,8 @@ end
-- Export both Theme and ThemeManager -- Export both Theme and ThemeManager
Theme.Manager = ThemeManager Theme.Manager = ThemeManager
---Validate a theme definition for structural correctness (non-aggressive) --- Check theme definitions for correctness before use to catch configuration errors early
--- Use this during development to verify custom themes are properly structured
---@param theme table? The theme to validate ---@param theme table? The theme to validate
---@param options table? Optional validation options {strict: boolean} ---@param options table? Optional validation options {strict: boolean}
---@return boolean valid, table errors List of validation errors ---@return boolean valid, table errors List of validation errors
@@ -908,7 +917,8 @@ function Theme.validateTheme(theme, options)
return #errors == 0, errors return #errors == 0, errors
end end
---Sanitize a theme definition by removing invalid values and providing defaults --- Clean up malformed theme data to make it usable without crashing
--- Use this to robustly handle user-created or external themes
---@param theme table? The theme to sanitize ---@param theme table? The theme to sanitize
---@return table sanitized The sanitized theme ---@return table sanitized The sanitized theme
function Theme.sanitizeTheme(theme) function Theme.sanitizeTheme(theme)

View File

@@ -86,12 +86,19 @@ function Transform.lerp(from, to, t)
if type(to) ~= "table" then if type(to) ~= "table" then
to = Transform.new() to = Transform.new()
end end
if type(t) ~= "number" or t ~= t or t == math.huge or t == -math.huge then if type(t) ~= "number" or t ~= t then
-- NaN or invalid type
t = 0 t = 0
end elseif t == math.huge then
-- Positive infinity
t = 1
elseif t == -math.huge then
-- Negative infinity
t = 0
else
-- Clamp t to 0-1 range -- Clamp t to 0-1 range
t = math.max(0, math.min(1, t)) t = math.max(0, math.min(1, t))
end
return Transform.new({ return Transform.new({
rotate = (from.rotate or 0) * (1 - t) + (to.rotate or 0) * t, rotate = (from.rotate or 0) * (1 - t) + (to.rotate or 0) * t,

View File

@@ -544,4 +544,6 @@ function TestAnimationProperties:testEdgeCase_ResultInvalidatedOnUpdate()
luaunit.assertAlmostEquals(result1.x, 75, 0.01) luaunit.assertAlmostEquals(result1.x, 75, 0.01)
end end
os.exit(luaunit.LuaUnit.run()) if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -92,55 +92,60 @@ end
-- Test: warn() prints with correct format (backward compatibility) -- Test: warn() prints with correct format (backward compatibility)
function TestErrorHandler:test_warn_prints_with_format() function TestErrorHandler:test_warn_prints_with_format()
-- Capture print output by mocking print -- Capture io.write output by mocking io.write
local captured = nil local captured = nil
local originalPrint = print local originalWrite = io.write
print = function(msg) io.write = function(msg)
captured = msg captured = msg
end end
ErrorHandler.setLogTarget("console")
ErrorHandler.warn("TestModule", "This is a warning") ErrorHandler.warn("TestModule", "This is a warning")
ErrorHandler.setLogTarget("none")
print = originalPrint io.write = originalWrite
luaunit.assertNotNil(captured, "warn() should print") luaunit.assertNotNil(captured, "warn() should print")
luaunit.assertEquals(captured, "[FlexLove - TestModule] Warning: This is a warning") luaunit.assertStrContains(captured, "[WARNING] [TestModule] This is a warning")
end end
-- Test: warn() with error code -- Test: warn() with error code
function TestErrorHandler:test_warn_with_code() function TestErrorHandler:test_warn_with_code()
local captured = nil local captured = nil
local originalPrint = print local originalWrite = io.write
print = function(msg) io.write = function(msg)
captured = msg captured = msg
end end
ErrorHandler.setLogTarget("console")
ErrorHandler.warn("TestModule", "VAL_001", "Potentially invalid property") ErrorHandler.warn("TestModule", "VAL_001", "Potentially invalid property")
ErrorHandler.setLogTarget("none")
print = originalPrint io.write = originalWrite
luaunit.assertNotNil(captured, "warn() should print") luaunit.assertNotNil(captured, "warn() should print")
luaunit.assertStrContains(captured, "[FlexLove - TestModule] Warning [FLEXLOVE_VAL_001]") luaunit.assertStrContains(captured, "[WARNING] [TestModule] [VAL_001]")
luaunit.assertStrContains(captured, "Potentially invalid property") luaunit.assertStrContains(captured, "Potentially invalid property")
end end
-- Test: warn() with details -- Test: warn() with details
function TestErrorHandler:test_warn_with_details() function TestErrorHandler:test_warn_with_details()
local captured = nil local captured = nil
local originalPrint = print local originalWrite = io.write
print = function(msg) io.write = function(msg)
captured = msg captured = (captured or "") .. msg
end end
ErrorHandler.setLogTarget("console")
ErrorHandler.warn("TestModule", "VAL_001", "Check this property", { ErrorHandler.warn("TestModule", "VAL_001", "Check this property", {
property = "height", property = "height",
value = "auto", value = "auto",
}) })
ErrorHandler.setLogTarget("none")
print = originalPrint io.write = originalWrite
luaunit.assertNotNil(captured, "warn() should print") luaunit.assertNotNil(captured, "warn() should print")
luaunit.assertStrContains(captured, "Details:")
luaunit.assertStrContains(captured, "Property: height") luaunit.assertStrContains(captured, "Property: height")
luaunit.assertStrContains(captured, "Value: auto") luaunit.assertStrContains(captured, "Value: auto")
end end
@@ -225,14 +230,16 @@ end
-- Test: warnDeprecated prints deprecation warning -- Test: warnDeprecated prints deprecation warning
function TestErrorHandler:test_warnDeprecated_prints_message() function TestErrorHandler:test_warnDeprecated_prints_message()
local captured = nil local captured = nil
local originalPrint = print local originalWrite = io.write
print = function(msg) io.write = function(msg)
captured = msg captured = msg
end end
ErrorHandler.setLogTarget("console")
ErrorHandler.warnDeprecated("TestModule", "oldFunction", "newFunction") ErrorHandler.warnDeprecated("TestModule", "oldFunction", "newFunction")
ErrorHandler.setLogTarget("none")
print = originalPrint io.write = originalWrite
luaunit.assertNotNil(captured, "warnDeprecated should print") luaunit.assertNotNil(captured, "warnDeprecated should print")
luaunit.assertStrContains(captured, "'oldFunction' is deprecated. Use 'newFunction' instead") luaunit.assertStrContains(captured, "'oldFunction' is deprecated. Use 'newFunction' instead")
@@ -241,14 +248,16 @@ end
-- Test: warnCommonMistake prints helpful message -- Test: warnCommonMistake prints helpful message
function TestErrorHandler:test_warnCommonMistake_prints_message() function TestErrorHandler:test_warnCommonMistake_prints_message()
local captured = nil local captured = nil
local originalPrint = print local originalWrite = io.write
print = function(msg) io.write = function(msg)
captured = msg captured = msg
end end
ErrorHandler.setLogTarget("console")
ErrorHandler.warnCommonMistake("TestModule", "Width is zero", "Set width to positive value") ErrorHandler.warnCommonMistake("TestModule", "Width is zero", "Set width to positive value")
ErrorHandler.setLogTarget("none")
print = originalPrint io.write = originalWrite
luaunit.assertNotNil(captured, "warnCommonMistake should print") luaunit.assertNotNil(captured, "warnCommonMistake should print")
luaunit.assertStrContains(captured, "Width is zero. Suggestion: Set width to positive value") luaunit.assertStrContains(captured, "Width is zero. Suggestion: Set width to positive value")

View File

@@ -21,7 +21,7 @@ end
function TestFlexLove:testModuleLoads() function TestFlexLove:testModuleLoads()
luaunit.assertNotNil(FlexLove) luaunit.assertNotNil(FlexLove)
luaunit.assertNotNil(FlexLove._VERSION) luaunit.assertNotNil(FlexLove._VERSION)
luaunit.assertEquals(FlexLove._VERSION, "0.2.2") luaunit.assertEquals(FlexLove._VERSION, "0.2.3")
luaunit.assertNotNil(FlexLove._DESCRIPTION) luaunit.assertNotNil(FlexLove._DESCRIPTION)
luaunit.assertNotNil(FlexLove._URL) luaunit.assertNotNil(FlexLove._URL)
luaunit.assertNotNil(FlexLove._LICENSE) luaunit.assertNotNil(FlexLove._LICENSE)

View File

@@ -16,8 +16,12 @@ TestImageTiling = {}
function TestImageTiling:setUp() function TestImageTiling:setUp()
-- Create a mock image -- Create a mock image
self.mockImage = { self.mockImage = {
getDimensions = function() return 64, 64 end, getDimensions = function()
type = function() return "Image" end, return 64, 64
end,
type = function()
return "Image"
end,
} }
end end
@@ -30,7 +34,7 @@ function TestImageTiling:testDrawTiledNoRepeat()
local drawCalls = {} local drawCalls = {}
local originalDraw = love.graphics.draw local originalDraw = love.graphics.draw
love.graphics.draw = function(...) love.graphics.draw = function(...)
table.insert(drawCalls, {...}) table.insert(drawCalls, { ... })
end end
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 1, nil) ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 1, nil)
@@ -51,7 +55,7 @@ function TestImageTiling:testDrawTiledRepeat()
local originalNewQuad = love.graphics.newQuad local originalNewQuad = love.graphics.newQuad
love.graphics.draw = function(...) love.graphics.draw = function(...)
table.insert(drawCalls, {...}) table.insert(drawCalls, { ... })
end end
love.graphics.newQuad = function(...) love.graphics.newQuad = function(...)
@@ -77,7 +81,7 @@ function TestImageTiling:testDrawTiledRepeatX()
local originalNewQuad = love.graphics.newQuad local originalNewQuad = love.graphics.newQuad
love.graphics.draw = function(...) love.graphics.draw = function(...)
table.insert(drawCalls, {...}) table.insert(drawCalls, { ... })
end end
love.graphics.newQuad = function(...) love.graphics.newQuad = function(...)
@@ -102,7 +106,7 @@ function TestImageTiling:testDrawTiledRepeatY()
local originalNewQuad = love.graphics.newQuad local originalNewQuad = love.graphics.newQuad
love.graphics.draw = function(...) love.graphics.draw = function(...)
table.insert(drawCalls, {...}) table.insert(drawCalls, { ... })
end end
love.graphics.newQuad = function(...) love.graphics.newQuad = function(...)
@@ -126,7 +130,7 @@ function TestImageTiling:testDrawTiledSpace()
local originalDraw = love.graphics.draw local originalDraw = love.graphics.draw
love.graphics.draw = function(...) love.graphics.draw = function(...)
table.insert(drawCalls, {...}) table.insert(drawCalls, { ... })
end end
-- Image is 64x64, bounds are 200x200 -- Image is 64x64, bounds are 200x200
@@ -144,7 +148,7 @@ function TestImageTiling:testDrawTiledRound()
local originalDraw = love.graphics.draw local originalDraw = love.graphics.draw
love.graphics.draw = function(...) love.graphics.draw = function(...)
table.insert(drawCalls, {...}) table.insert(drawCalls, { ... })
end end
-- Image is 64x64, bounds are 200x200 -- Image is 64x64, bounds are 200x200
@@ -162,7 +166,7 @@ function TestImageTiling:testDrawTiledWithOpacity()
local originalSetColor = love.graphics.setColor local originalSetColor = love.graphics.setColor
love.graphics.setColor = function(...) love.graphics.setColor = function(...)
table.insert(setColorCalls, {...}) table.insert(setColorCalls, { ... })
end end
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 0.5, nil) ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 0.5, nil)
@@ -188,7 +192,7 @@ function TestImageTiling:testDrawTiledWithTint()
local originalSetColor = love.graphics.setColor local originalSetColor = love.graphics.setColor
love.graphics.setColor = function(...) love.graphics.setColor = function(...)
table.insert(setColorCalls, {...}) table.insert(setColorCalls, { ... })
end end
local redTint = Color.new(1, 0, 0, 1) local redTint = Color.new(1, 0, 0, 1)
@@ -401,5 +405,6 @@ function TestImageTiling:testElementSetImageOpacity()
luaunit.assertEquals(element.imageOpacity, 0.7) luaunit.assertEquals(element.imageOpacity, 0.7)
end end
-- Run the tests if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run()) os.exit(luaunit.LuaUnit.run())
end

View File

@@ -46,7 +46,9 @@ function TestPerformanceInstrumentation:testMultipleTimers()
Performance.startTimer("render") Performance.startTimer("render")
local sum = 0 local sum = 0
for i = 1, 100 do sum = sum + i end for i = 1, 100 do
sum = sum + i
end
Performance.stopTimer("layout") Performance.stopTimer("layout")
Performance.stopTimer("render") Performance.stopTimer("render")
@@ -159,9 +161,6 @@ function TestPerformanceInstrumentation:testExportCSV()
luaunit.assertTrue(string.find(csv, "test_op") ~= nil) luaunit.assertTrue(string.find(csv, "test_op") ~= nil)
end end
-- Run tests if executed directly if not _G.RUNNING_ALL_TESTS then
if arg and arg[0]:find("performance_instrumentation_test%.lua$") then
os.exit(luaunit.LuaUnit.run()) os.exit(luaunit.LuaUnit.run())
end end
return TestPerformanceInstrumentation

View File

@@ -153,4 +153,6 @@ function TestPerformanceWarnings:testLayoutRecalculationTracking()
luaunit.assertNotNil(root) luaunit.assertNotNil(root)
end end
return TestPerformanceWarnings if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -243,8 +243,8 @@ function TestTransform:testClone_AllProperties()
luaunit.assertAlmostEquals(clone.originX, 0.25, 0.01) luaunit.assertAlmostEquals(clone.originX, 0.25, 0.01)
luaunit.assertAlmostEquals(clone.originY, 0.75, 0.01) luaunit.assertAlmostEquals(clone.originY, 0.75, 0.01)
-- Ensure it's a different object -- Ensure it's a different object (use raw comparison)
luaunit.assertNotEquals(clone, original) luaunit.assertFalse(rawequal(clone, original), "Clone should be a different table instance")
end end
function TestTransform:testClone_Nil() function TestTransform:testClone_Nil()
@@ -289,4 +289,6 @@ function TestTransform:testTransformAnimation()
luaunit.assertAlmostEquals(result.transform.scaleX, 1.5, 0.01) luaunit.assertAlmostEquals(result.transform.scaleX, 1.5, 0.01)
end end
os.exit(luaunit.LuaUnit.run()) if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -105,6 +105,9 @@ function love_helper.graphics.newCanvas(width, height)
getDimensions = function() getDimensions = function()
return width or mockWindowWidth, height or mockWindowHeight return width or mockWindowWidth, height or mockWindowHeight
end, end,
release = function()
-- Mock canvas release
end,
} }
end end