consolidated patterns
This commit is contained in:
29
FlexLove.lua
29
FlexLove.lua
@@ -89,25 +89,16 @@ if ImageDataReader.init then
|
|||||||
ImageDataReader.init(errorHandlerDeps)
|
ImageDataReader.init(errorHandlerDeps)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Initialize Units module with Context dependency
|
-- Initialize modules with dependencies
|
||||||
Units.initialize(Context)
|
Units.init({ Context = Context, ErrorHandler = ErrorHandler })
|
||||||
Units.initializeErrorHandler(ErrorHandler)
|
Color.init({ ErrorHandler = ErrorHandler })
|
||||||
|
utils.init({ ErrorHandler = ErrorHandler })
|
||||||
-- Initialize ErrorHandler for Color module
|
Animation.init({ ErrorHandler = ErrorHandler, Easing = Easing, Color = Color })
|
||||||
Color.initializeErrorHandler(ErrorHandler)
|
AnimationGroup.init({ ErrorHandler = ErrorHandler })
|
||||||
|
|
||||||
-- Initialize ErrorHandler for utils
|
|
||||||
utils.initializeErrorHandler(ErrorHandler)
|
|
||||||
|
|
||||||
-- Initialize ErrorHandler for Animation module
|
|
||||||
Animation.initializeErrorHandler(ErrorHandler)
|
|
||||||
|
|
||||||
-- Initialize ErrorHandler for AnimationGroup module
|
|
||||||
AnimationGroup.initializeErrorHandler(ErrorHandler)
|
|
||||||
|
|
||||||
-- Add version and metadata
|
-- Add version and metadata
|
||||||
flexlove._VERSION = "0.2.3"
|
flexlove._VERSION = "0.2.3"
|
||||||
flexlove._DESCRIPTION = "0I Library for LÖVE Framework based on flexbox"
|
flexlove._DESCRIPTION = "UI Library for LÖVE Framework based on flexbox"
|
||||||
flexlove._URL = "https://github.com/mikefreno/FlexLove"
|
flexlove._URL = "https://github.com/mikefreno/FlexLove"
|
||||||
flexlove._LICENSE = [[
|
flexlove._LICENSE = [[
|
||||||
MIT License
|
MIT License
|
||||||
@@ -281,12 +272,12 @@ function flexlove.executeDeferredCallbacks()
|
|||||||
if #flexlove._deferredCallbacks == 0 then
|
if #flexlove._deferredCallbacks == 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Copy callbacks and clear queue before execution
|
-- Copy callbacks and clear queue before execution
|
||||||
-- This prevents infinite loops if callbacks defer more callbacks
|
-- This prevents infinite loops if callbacks defer more callbacks
|
||||||
local callbacks = flexlove._deferredCallbacks
|
local callbacks = flexlove._deferredCallbacks
|
||||||
flexlove._deferredCallbacks = {}
|
flexlove._deferredCallbacks = {}
|
||||||
|
|
||||||
for _, callback in ipairs(callbacks) do
|
for _, callback in ipairs(callbacks) do
|
||||||
local success, err = xpcall(callback, debug.traceback)
|
local success, err = xpcall(callback, debug.traceback)
|
||||||
if not success then
|
if not success then
|
||||||
@@ -553,7 +544,7 @@ function flexlove.draw(gameDrawFunc, postDrawFunc)
|
|||||||
Performance.renderHUD()
|
Performance.renderHUD()
|
||||||
|
|
||||||
love.graphics.setCanvas(outerCanvas)
|
love.graphics.setCanvas(outerCanvas)
|
||||||
|
|
||||||
-- NOTE: Deferred callbacks are NOT executed here because the calling code
|
-- NOTE: Deferred callbacks are NOT executed here because the calling code
|
||||||
-- (e.g., main.lua) might still have a canvas active. Callbacks must be
|
-- (e.g., main.lua) might still have a canvas active. Callbacks must be
|
||||||
-- executed by calling FlexLove.executeDeferredCallbacks() at the very end
|
-- executed by calling FlexLove.executeDeferredCallbacks() at the very end
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
--- Easing function type
|
|
||||||
---@alias EasingFunction fun(t: number): number
|
|
||||||
|
|
||||||
-- ErrorHandler dependency (injected via initializeErrorHandler)
|
|
||||||
local ErrorHandler = nil
|
local ErrorHandler = nil
|
||||||
|
local Easing = nil
|
||||||
-- Easing module for easing functions
|
local Color = nil
|
||||||
local Easing = require("modules.Easing")
|
|
||||||
---@class Keyframe
|
---@class Keyframe
|
||||||
---@field at number Normalized time position (0-1)
|
---@field at number Normalized time position (0-1)
|
||||||
---@field values table Property values at this keyframe
|
---@field values table Property values at this keyframe
|
||||||
@@ -47,19 +42,19 @@ function Animation.new(props)
|
|||||||
-- Validate input
|
-- Validate input
|
||||||
if type(props) ~= "table" then
|
if type(props) ~= "table" then
|
||||||
ErrorHandler.warn("Animation", "Animation.new() requires a table argument. Using default values.")
|
ErrorHandler.warn("Animation", "Animation.new() requires a table argument. Using default values.")
|
||||||
props = {duration = 1, start = {}, final = {}}
|
props = { duration = 1, start = {}, final = {} }
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(props.duration) ~= "number" or props.duration <= 0 then
|
if type(props.duration) ~= "number" or props.duration <= 0 then
|
||||||
ErrorHandler.warn("Animation", "Animation duration must be a positive number. Using 1 second.")
|
ErrorHandler.warn("Animation", "Animation duration must be a positive number. Using 1 second.")
|
||||||
props.duration = 1
|
props.duration = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(props.start) ~= "table" then
|
if type(props.start) ~= "table" then
|
||||||
ErrorHandler.warn("Animation", "Animation start must be a table. Using empty table.")
|
ErrorHandler.warn("Animation", "Animation start must be a table. Using empty table.")
|
||||||
props.start = {}
|
props.start = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(props.final) ~= "table" then
|
if type(props.final) ~= "table" then
|
||||||
ErrorHandler.warn("Animation", "Animation final must be a table. Using empty table.")
|
ErrorHandler.warn("Animation", "Animation final must be a table. Using empty table.")
|
||||||
props.final = {}
|
props.final = {}
|
||||||
@@ -114,12 +109,12 @@ function Animation:update(dt, element)
|
|||||||
if type(dt) ~= "number" or dt < 0 or dt ~= dt or dt == math.huge then
|
if type(dt) ~= "number" or dt < 0 or dt ~= dt or dt == math.huge then
|
||||||
dt = 0
|
dt = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Don't update if paused
|
-- Don't update if paused
|
||||||
if self._paused then
|
if self._paused then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle delay
|
-- Handle delay
|
||||||
if self._delay and self._delayElapsed then
|
if self._delay and self._delayElapsed then
|
||||||
if self._delayElapsed < self._delay then
|
if self._delayElapsed < self._delay then
|
||||||
@@ -127,7 +122,7 @@ function Animation:update(dt, element)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Call onStart on first update
|
-- Call onStart on first update
|
||||||
if not self._hasStarted then
|
if not self._hasStarted then
|
||||||
self._hasStarted = true
|
self._hasStarted = true
|
||||||
@@ -140,10 +135,10 @@ function Animation:update(dt, element)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply speed multiplier
|
-- Apply speed multiplier
|
||||||
dt = dt * self._speed
|
dt = dt * self._speed
|
||||||
|
|
||||||
-- Update elapsed time (reversed if needed)
|
-- Update elapsed time (reversed if needed)
|
||||||
if self._reversed then
|
if self._reversed then
|
||||||
self.elapsed = self.elapsed - dt
|
self.elapsed = self.elapsed - dt
|
||||||
@@ -165,11 +160,11 @@ function Animation:update(dt, element)
|
|||||||
if self.elapsed >= self.duration then
|
if self.elapsed >= self.duration then
|
||||||
self.elapsed = self.duration
|
self.elapsed = self.duration
|
||||||
self._resultDirty = true
|
self._resultDirty = true
|
||||||
|
|
||||||
-- Handle repeat and yoyo
|
-- Handle repeat and yoyo
|
||||||
if self._repeatCount then
|
if self._repeatCount then
|
||||||
self._repeatCurrent = (self._repeatCurrent or 0) + 1
|
self._repeatCurrent = (self._repeatCurrent or 0) + 1
|
||||||
|
|
||||||
if self._repeatCount == 0 or self._repeatCurrent < self._repeatCount then
|
if self._repeatCount == 0 or self._repeatCurrent < self._repeatCount then
|
||||||
-- Continue repeating
|
-- Continue repeating
|
||||||
if self._yoyo then
|
if self._yoyo then
|
||||||
@@ -187,7 +182,7 @@ function Animation:update(dt, element)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Animation truly completed
|
-- Animation truly completed
|
||||||
self._state = "completed"
|
self._state = "completed"
|
||||||
-- Call onComplete callback
|
-- Call onComplete callback
|
||||||
@@ -200,9 +195,9 @@ function Animation:update(dt, element)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self._resultDirty = true
|
self._resultDirty = true
|
||||||
|
|
||||||
-- Call onUpdate callback
|
-- Call onUpdate callback
|
||||||
if self.onUpdate and type(self.onUpdate) == "function" then
|
if self.onUpdate and type(self.onUpdate) == "function" then
|
||||||
local progress = self.elapsed / self.duration
|
local progress = self.elapsed / self.duration
|
||||||
@@ -211,7 +206,7 @@ function Animation:update(dt, element)
|
|||||||
print(string.format("[Animation] onUpdate error: %s", tostring(err)))
|
print(string.format("[Animation] onUpdate error: %s", tostring(err)))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -231,15 +226,21 @@ end
|
|||||||
---@param ColorModule table Color module reference
|
---@param ColorModule table Color module reference
|
||||||
---@return any interpolated Interpolated Color instance
|
---@return any interpolated Interpolated Color instance
|
||||||
local function lerpColor(startColor, finalColor, easedT, ColorModule)
|
local function lerpColor(startColor, finalColor, easedT, ColorModule)
|
||||||
if not ColorModule then
|
-- Use provided ColorModule or fall back to module-level Color or static _ColorModule
|
||||||
return nil
|
local CM = ColorModule or Color or Animation._ColorModule
|
||||||
|
|
||||||
|
if not CM or not CM.parse or not CM.lerp then
|
||||||
|
if ErrorHandler then
|
||||||
|
ErrorHandler.warn("Animation", "Color module not properly initialized. Cannot interpolate colors.")
|
||||||
|
end
|
||||||
|
return startColor -- Return start color as fallback
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Parse colors if needed
|
-- Parse colors if needed
|
||||||
local colorA = ColorModule.parse(startColor)
|
local colorA = CM.parse(startColor)
|
||||||
local colorB = ColorModule.parse(finalColor)
|
local colorB = CM.parse(finalColor)
|
||||||
|
|
||||||
return ColorModule.lerp(colorA, colorB, easedT)
|
return CM.lerp(colorA, colorB, easedT)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Helper function to interpolate table values (padding, margin, cornerRadius)
|
--- Helper function to interpolate table values (padding, margin, cornerRadius)
|
||||||
@@ -249,16 +250,20 @@ end
|
|||||||
---@return table interpolated Interpolated table
|
---@return table interpolated Interpolated table
|
||||||
local function lerpTable(startTable, finalTable, easedT)
|
local function lerpTable(startTable, finalTable, easedT)
|
||||||
local result = {}
|
local result = {}
|
||||||
|
|
||||||
-- Iterate through all keys in both tables
|
-- Iterate through all keys in both tables
|
||||||
local keys = {}
|
local keys = {}
|
||||||
for k in pairs(startTable) do keys[k] = true end
|
for k in pairs(startTable) do
|
||||||
for k in pairs(finalTable) do keys[k] = true end
|
keys[k] = true
|
||||||
|
end
|
||||||
|
for k in pairs(finalTable) do
|
||||||
|
keys[k] = true
|
||||||
|
end
|
||||||
|
|
||||||
for key in pairs(keys) do
|
for key in pairs(keys) do
|
||||||
local startVal = startTable[key]
|
local startVal = startTable[key]
|
||||||
local finalVal = finalTable[key]
|
local finalVal = finalTable[key]
|
||||||
|
|
||||||
if type(startVal) == "number" and type(finalVal) == "number" then
|
if type(startVal) == "number" and type(finalVal) == "number" then
|
||||||
result[key] = lerpNumber(startVal, finalVal, easedT)
|
result[key] = lerpNumber(startVal, finalVal, easedT)
|
||||||
elseif startVal ~= nil then
|
elseif startVal ~= nil then
|
||||||
@@ -267,7 +272,7 @@ local function lerpTable(startTable, finalTable, easedT)
|
|||||||
result[key] = finalVal
|
result[key] = finalVal
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -279,11 +284,11 @@ function Animation:findKeyframes(progress)
|
|||||||
if not self.keyframes or #self.keyframes < 2 then
|
if not self.keyframes or #self.keyframes < 2 then
|
||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Find surrounding keyframes
|
-- Find surrounding keyframes
|
||||||
local prevFrame = self.keyframes[1]
|
local prevFrame = self.keyframes[1]
|
||||||
local nextFrame = self.keyframes[#self.keyframes]
|
local nextFrame = self.keyframes[#self.keyframes]
|
||||||
|
|
||||||
for i = 1, #self.keyframes - 1 do
|
for i = 1, #self.keyframes - 1 do
|
||||||
if progress >= self.keyframes[i].at and progress <= self.keyframes[i + 1].at then
|
if progress >= self.keyframes[i].at and progress <= self.keyframes[i + 1].at then
|
||||||
prevFrame = self.keyframes[i]
|
prevFrame = self.keyframes[i]
|
||||||
@@ -291,7 +296,7 @@ function Animation:findKeyframes(progress)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return prevFrame, nextFrame
|
return prevFrame, nextFrame
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -302,50 +307,74 @@ end
|
|||||||
---@return table result Interpolated values
|
---@return table result Interpolated values
|
||||||
function Animation:lerpKeyframes(prevFrame, nextFrame, easedT)
|
function Animation:lerpKeyframes(prevFrame, nextFrame, easedT)
|
||||||
local result = {}
|
local result = {}
|
||||||
|
|
||||||
-- Get all unique property keys
|
-- Get all unique property keys
|
||||||
local keys = {}
|
local keys = {}
|
||||||
for k in pairs(prevFrame.values) do keys[k] = true end
|
for k in pairs(prevFrame.values) do
|
||||||
for k in pairs(nextFrame.values) do keys[k] = true end
|
keys[k] = true
|
||||||
|
end
|
||||||
|
for k in pairs(nextFrame.values) do
|
||||||
|
keys[k] = true
|
||||||
|
end
|
||||||
|
|
||||||
-- Define properties that should be animated as numbers
|
-- Define properties that should be animated as numbers
|
||||||
local numericProperties = {
|
local numericProperties = {
|
||||||
"width", "height", "opacity", "x", "y",
|
"width",
|
||||||
"gap", "imageOpacity", "scrollbarWidth",
|
"height",
|
||||||
"borderWidth", "fontSize", "lineHeight"
|
"opacity",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"gap",
|
||||||
|
"imageOpacity",
|
||||||
|
"scrollbarWidth",
|
||||||
|
"borderWidth",
|
||||||
|
"fontSize",
|
||||||
|
"lineHeight",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Define properties that should be animated as Colors
|
-- Define properties that should be animated as Colors
|
||||||
local colorProperties = {
|
local colorProperties = {
|
||||||
"backgroundColor", "borderColor", "textColor",
|
"backgroundColor",
|
||||||
"scrollbarColor", "scrollbarBackgroundColor", "imageTint"
|
"borderColor",
|
||||||
|
"textColor",
|
||||||
|
"scrollbarColor",
|
||||||
|
"scrollbarBackgroundColor",
|
||||||
|
"imageTint",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Define properties that should be animated as tables
|
-- Define properties that should be animated as tables
|
||||||
local tableProperties = {
|
local tableProperties = {
|
||||||
"padding", "margin", "cornerRadius"
|
"padding",
|
||||||
|
"margin",
|
||||||
|
"cornerRadius",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Create lookup sets for faster property type checking
|
-- Create lookup sets for faster property type checking
|
||||||
local numericSet = {}
|
local numericSet = {}
|
||||||
for _, prop in ipairs(numericProperties) do numericSet[prop] = true end
|
for _, prop in ipairs(numericProperties) do
|
||||||
|
numericSet[prop] = true
|
||||||
|
end
|
||||||
|
|
||||||
local colorSet = {}
|
local colorSet = {}
|
||||||
for _, prop in ipairs(colorProperties) do colorSet[prop] = true end
|
for _, prop in ipairs(colorProperties) do
|
||||||
|
colorSet[prop] = true
|
||||||
|
end
|
||||||
|
|
||||||
local tableSet = {}
|
local tableSet = {}
|
||||||
for _, prop in ipairs(tableProperties) do tableSet[prop] = true end
|
for _, prop in ipairs(tableProperties) do
|
||||||
|
tableSet[prop] = true
|
||||||
|
end
|
||||||
|
|
||||||
-- Interpolate each property
|
-- Interpolate each property
|
||||||
for key in pairs(keys) do
|
for key in pairs(keys) do
|
||||||
local startVal = prevFrame.values[key]
|
local startVal = prevFrame.values[key]
|
||||||
local finalVal = nextFrame.values[key]
|
local finalVal = nextFrame.values[key]
|
||||||
|
|
||||||
if numericSet[key] and type(startVal) == "number" and type(finalVal) == "number" then
|
if numericSet[key] and type(startVal) == "number" and type(finalVal) == "number" then
|
||||||
result[key] = lerpNumber(startVal, finalVal, easedT)
|
result[key] = lerpNumber(startVal, finalVal, easedT)
|
||||||
elseif colorSet[key] and self._Color then
|
elseif colorSet[key] and (self._Color or Animation._ColorModule) then
|
||||||
if startVal ~= nil and finalVal ~= nil then
|
if startVal ~= nil and finalVal ~= nil then
|
||||||
result[key] = lerpColor(startVal, finalVal, easedT, self._Color)
|
result[key] = lerpColor(startVal, finalVal, easedT, self._Color or Animation._ColorModule)
|
||||||
end
|
end
|
||||||
elseif tableSet[key] and type(startVal) == "table" and type(finalVal) == "table" then
|
elseif tableSet[key] and type(startVal) == "table" and type(finalVal) == "table" then
|
||||||
result[key] = lerpTable(startVal, finalVal, easedT)
|
result[key] = lerpTable(startVal, finalVal, easedT)
|
||||||
@@ -359,7 +388,7 @@ function Animation:lerpKeyframes(prevFrame, nextFrame, easedT)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -373,18 +402,18 @@ function Animation:interpolate()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local t = math.min(self.elapsed / self.duration, 1)
|
local t = math.min(self.elapsed / self.duration, 1)
|
||||||
|
|
||||||
-- Handle keyframe animations
|
-- Handle keyframe animations
|
||||||
if self.keyframes and #self.keyframes >= 2 then
|
if self.keyframes and type(self.keyframes) == "table" and #self.keyframes >= 2 then
|
||||||
local prevFrame, nextFrame = self:findKeyframes(t)
|
local prevFrame, nextFrame = self:findKeyframes(t)
|
||||||
|
|
||||||
if prevFrame and nextFrame then
|
if prevFrame and nextFrame then
|
||||||
-- Calculate local progress between keyframes
|
-- Calculate local progress between keyframes
|
||||||
local localProgress = 0
|
local localProgress = 0
|
||||||
if nextFrame.at > prevFrame.at then
|
if nextFrame.at > prevFrame.at then
|
||||||
localProgress = (t - prevFrame.at) / (nextFrame.at - prevFrame.at)
|
localProgress = (t - prevFrame.at) / (nextFrame.at - prevFrame.at)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply per-keyframe easing
|
-- Apply per-keyframe easing
|
||||||
local easingFn = Easing.linear
|
local easingFn = Easing.linear
|
||||||
if prevFrame.easing then
|
if prevFrame.easing then
|
||||||
@@ -394,15 +423,15 @@ function Animation:interpolate()
|
|||||||
easingFn = prevFrame.easing
|
easingFn = prevFrame.easing
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local success, easedT = pcall(easingFn, localProgress)
|
local success, easedT = pcall(easingFn, localProgress)
|
||||||
if not success or type(easedT) ~= "number" or easedT ~= easedT or easedT == math.huge or easedT == -math.huge then
|
if not success or type(easedT) ~= "number" or easedT ~= easedT or easedT == math.huge or easedT == -math.huge then
|
||||||
easedT = localProgress
|
easedT = localProgress
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Interpolate between keyframes
|
-- Interpolate between keyframes
|
||||||
local keyframeResult = self:lerpKeyframes(prevFrame, nextFrame, easedT)
|
local keyframeResult = self:lerpKeyframes(prevFrame, nextFrame, easedT)
|
||||||
|
|
||||||
-- Copy to cached result
|
-- Copy to cached result
|
||||||
local result = self._cachedResult
|
local result = self._cachedResult
|
||||||
for k in pairs(result) do
|
for k in pairs(result) do
|
||||||
@@ -411,71 +440,86 @@ function Animation:interpolate()
|
|||||||
for k, v in pairs(keyframeResult) do
|
for k, v in pairs(keyframeResult) do
|
||||||
result[k] = v
|
result[k] = v
|
||||||
end
|
end
|
||||||
|
|
||||||
self._resultDirty = false
|
self._resultDirty = false
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Standard interpolation (non-keyframe)
|
-- Standard interpolation (non-keyframe)
|
||||||
-- Apply easing function with protection
|
-- Apply easing function with protection
|
||||||
local success, easedT = pcall(self.easing, t)
|
local success, easedT = pcall(self.easing, t)
|
||||||
if not success or type(easedT) ~= "number" or easedT ~= easedT or easedT == math.huge or easedT == -math.huge then
|
if not success or type(easedT) ~= "number" or easedT ~= easedT or easedT == math.huge or easedT == -math.huge then
|
||||||
easedT = t -- Fallback to linear if easing fails
|
easedT = t -- Fallback to linear if easing fails
|
||||||
end
|
end
|
||||||
|
|
||||||
local result = self._cachedResult -- Reuse existing table
|
local result = self._cachedResult -- Reuse existing table
|
||||||
|
|
||||||
-- Clear previous results
|
-- Clear previous results
|
||||||
for k in pairs(result) do
|
for k in pairs(result) do
|
||||||
result[k] = nil
|
result[k] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Define properties that should be animated as numbers
|
-- Define properties that should be animated as numbers
|
||||||
local numericProperties = {
|
local numericProperties = {
|
||||||
"width", "height", "opacity", "x", "y",
|
"width",
|
||||||
"gap", "imageOpacity", "scrollbarWidth",
|
"height",
|
||||||
"borderWidth", "fontSize", "lineHeight"
|
"opacity",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"gap",
|
||||||
|
"imageOpacity",
|
||||||
|
"scrollbarWidth",
|
||||||
|
"borderWidth",
|
||||||
|
"fontSize",
|
||||||
|
"lineHeight",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Define properties that should be animated as Colors
|
-- Define properties that should be animated as Colors
|
||||||
local colorProperties = {
|
local colorProperties = {
|
||||||
"backgroundColor", "borderColor", "textColor",
|
"backgroundColor",
|
||||||
"scrollbarColor", "scrollbarBackgroundColor", "imageTint"
|
"borderColor",
|
||||||
|
"textColor",
|
||||||
|
"scrollbarColor",
|
||||||
|
"scrollbarBackgroundColor",
|
||||||
|
"imageTint",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Define properties that should be animated as tables
|
-- Define properties that should be animated as tables
|
||||||
local tableProperties = {
|
local tableProperties = {
|
||||||
"padding", "margin", "cornerRadius"
|
"padding",
|
||||||
|
"margin",
|
||||||
|
"cornerRadius",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Interpolate numeric properties
|
-- Interpolate numeric properties
|
||||||
for _, prop in ipairs(numericProperties) do
|
for _, prop in ipairs(numericProperties) do
|
||||||
local startVal = self.start[prop]
|
local startVal = self.start[prop]
|
||||||
local finalVal = self.final[prop]
|
local finalVal = self.final[prop]
|
||||||
|
|
||||||
if type(startVal) == "number" and type(finalVal) == "number" then
|
if type(startVal) == "number" and type(finalVal) == "number" then
|
||||||
result[prop] = lerpNumber(startVal, finalVal, easedT)
|
result[prop] = lerpNumber(startVal, finalVal, easedT)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Interpolate color properties (if Color module is available)
|
-- Interpolate color properties (if Color module is available)
|
||||||
if self._Color then
|
local ColorModule = self._Color or Animation._ColorModule
|
||||||
|
if ColorModule then
|
||||||
for _, prop in ipairs(colorProperties) do
|
for _, prop in ipairs(colorProperties) do
|
||||||
local startVal = self.start[prop]
|
local startVal = self.start[prop]
|
||||||
local finalVal = self.final[prop]
|
local finalVal = self.final[prop]
|
||||||
|
|
||||||
if startVal ~= nil and finalVal ~= nil then
|
if startVal ~= nil and finalVal ~= nil then
|
||||||
result[prop] = lerpColor(startVal, finalVal, easedT, self._Color)
|
result[prop] = lerpColor(startVal, finalVal, easedT, ColorModule)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Interpolate table properties
|
-- Interpolate table properties
|
||||||
for _, prop in ipairs(tableProperties) do
|
for _, prop in ipairs(tableProperties) do
|
||||||
local startVal = self.start[prop]
|
local startVal = self.start[prop]
|
||||||
local finalVal = self.final[prop]
|
local finalVal = self.final[prop]
|
||||||
|
|
||||||
if type(startVal) == "table" and type(finalVal) == "table" then
|
if type(startVal) == "table" and type(finalVal) == "table" then
|
||||||
result[prop] = lerpTable(startVal, finalVal, easedT)
|
result[prop] = lerpTable(startVal, finalVal, easedT)
|
||||||
end
|
end
|
||||||
@@ -504,7 +548,7 @@ function Animation:apply(element)
|
|||||||
if not ErrorHandler then
|
if not ErrorHandler then
|
||||||
ErrorHandler = require("modules.ErrorHandler")
|
ErrorHandler = require("modules.ErrorHandler")
|
||||||
end
|
end
|
||||||
|
|
||||||
if not element or type(element) ~= "table" then
|
if not element or type(element) ~= "table" then
|
||||||
ErrorHandler.warn("Animation", "Cannot apply animation to nil or non-table element. Animation not applied.")
|
ErrorHandler.warn("Animation", "Cannot apply animation to nil or non-table element. Animation not applied.")
|
||||||
return
|
return
|
||||||
@@ -635,7 +679,7 @@ function Animation:chain(nextAnimation)
|
|||||||
if not ErrorHandler then
|
if not ErrorHandler then
|
||||||
ErrorHandler = require("modules.ErrorHandler")
|
ErrorHandler = require("modules.ErrorHandler")
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(nextAnimation) == "function" then
|
if type(nextAnimation) == "function" then
|
||||||
self._nextFactory = nextAnimation
|
self._nextFactory = nextAnimation
|
||||||
return self
|
return self
|
||||||
@@ -656,7 +700,7 @@ function Animation:delay(seconds)
|
|||||||
if not ErrorHandler then
|
if not ErrorHandler then
|
||||||
ErrorHandler = require("modules.ErrorHandler")
|
ErrorHandler = require("modules.ErrorHandler")
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(seconds) ~= "number" or seconds < 0 then
|
if type(seconds) ~= "number" or seconds < 0 then
|
||||||
ErrorHandler.warn("Animation", "delay() requires a non-negative number. Using 0.")
|
ErrorHandler.warn("Animation", "delay() requires a non-negative number. Using 0.")
|
||||||
seconds = 0
|
seconds = 0
|
||||||
@@ -674,7 +718,7 @@ function Animation:repeatCount(count)
|
|||||||
if not ErrorHandler then
|
if not ErrorHandler then
|
||||||
ErrorHandler = require("modules.ErrorHandler")
|
ErrorHandler = require("modules.ErrorHandler")
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(count) ~= "number" or count < 0 then
|
if type(count) ~= "number" or count < 0 then
|
||||||
ErrorHandler.warn("Animation", "repeatCount() requires a non-negative number. Using 0.")
|
ErrorHandler.warn("Animation", "repeatCount() requires a non-negative number. Using 0.")
|
||||||
count = 0
|
count = 0
|
||||||
@@ -714,7 +758,7 @@ function Animation.fade(duration, fromOpacity, toOpacity, easing)
|
|||||||
if type(toOpacity) ~= "number" then
|
if type(toOpacity) ~= "number" then
|
||||||
toOpacity = 0
|
toOpacity = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
return Animation.new({
|
return Animation.new({
|
||||||
duration = duration,
|
duration = duration,
|
||||||
start = { opacity = fromOpacity },
|
start = { opacity = fromOpacity },
|
||||||
@@ -743,7 +787,7 @@ function Animation.scale(duration, fromScale, toScale, easing)
|
|||||||
if type(toScale) ~= "table" then
|
if type(toScale) ~= "table" then
|
||||||
toScale = { width = 1, height = 1 }
|
toScale = { width = 1, height = 1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
return Animation.new({
|
return Animation.new({
|
||||||
duration = duration,
|
duration = duration,
|
||||||
start = { width = fromScale.width or 0, height = fromScale.height or 0 },
|
start = { width = fromScale.width or 0, height = fromScale.height or 0 },
|
||||||
@@ -762,26 +806,26 @@ function Animation.keyframes(props)
|
|||||||
if not ErrorHandler then
|
if not ErrorHandler then
|
||||||
ErrorHandler = require("modules.ErrorHandler")
|
ErrorHandler = require("modules.ErrorHandler")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Validate input
|
-- Validate input
|
||||||
if type(props) ~= "table" then
|
if type(props) ~= "table" then
|
||||||
ErrorHandler.warn("Animation", "Animation.keyframes() requires a table argument. Using default values.")
|
ErrorHandler.warn("Animation", "Animation.keyframes() requires a table argument. Using default values.")
|
||||||
props = {duration = 1, keyframes = {}}
|
props = { duration = 1, keyframes = {} }
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(props.duration) ~= "number" or props.duration <= 0 then
|
if type(props.duration) ~= "number" or props.duration <= 0 then
|
||||||
ErrorHandler.warn("Animation", "Keyframe animation duration must be a positive number. Using 1 second.")
|
ErrorHandler.warn("Animation", "Keyframe animation duration must be a positive number. Using 1 second.")
|
||||||
props.duration = 1
|
props.duration = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(props.keyframes) ~= "table" or #props.keyframes < 2 then
|
if type(props.keyframes) ~= "table" or #props.keyframes < 2 then
|
||||||
ErrorHandler.warn("Animation", "Keyframe animation requires at least 2 keyframes. Using empty animation.")
|
ErrorHandler.warn("Animation", "Keyframe animation requires at least 2 keyframes. Using empty animation.")
|
||||||
props.keyframes = {
|
props.keyframes = {
|
||||||
{at = 0, values = {}},
|
{ at = 0, values = {} },
|
||||||
{at = 1, values = {}}
|
{ at = 1, values = {} },
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sort keyframes by 'at' position
|
-- Sort keyframes by 'at' position
|
||||||
local sortedKeyframes = {}
|
local sortedKeyframes = {}
|
||||||
for i, kf in ipairs(props.keyframes) do
|
for i, kf in ipairs(props.keyframes) do
|
||||||
@@ -789,19 +833,21 @@ function Animation.keyframes(props)
|
|||||||
table.insert(sortedKeyframes, kf)
|
table.insert(sortedKeyframes, kf)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
table.sort(sortedKeyframes, function(a, b) return a.at < b.at end)
|
table.sort(sortedKeyframes, function(a, b)
|
||||||
|
return a.at < b.at
|
||||||
|
end)
|
||||||
|
|
||||||
-- Ensure keyframes start at 0 and end at 1
|
-- Ensure keyframes start at 0 and end at 1
|
||||||
if #sortedKeyframes > 0 then
|
if #sortedKeyframes > 0 then
|
||||||
if sortedKeyframes[1].at > 0 then
|
if sortedKeyframes[1].at > 0 then
|
||||||
table.insert(sortedKeyframes, 1, {at = 0, values = sortedKeyframes[1].values})
|
table.insert(sortedKeyframes, 1, { at = 0, values = sortedKeyframes[1].values })
|
||||||
end
|
end
|
||||||
if sortedKeyframes[#sortedKeyframes].at < 1 then
|
if sortedKeyframes[#sortedKeyframes].at < 1 then
|
||||||
table.insert(sortedKeyframes, {at = 1, values = sortedKeyframes[#sortedKeyframes].values})
|
table.insert(sortedKeyframes, { at = 1, values = sortedKeyframes[#sortedKeyframes].values })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Create animation with keyframes
|
-- Create animation with keyframes
|
||||||
return Animation.new({
|
return Animation.new({
|
||||||
duration = props.duration,
|
duration = props.duration,
|
||||||
@@ -815,13 +861,22 @@ function Animation.keyframes(props)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Initialize ErrorHandler dependency
|
--- Initialize dependencies
|
||||||
---@param errorHandler table The ErrorHandler module
|
---@param deps table Dependencies: { ErrorHandler = ErrorHandler, Easing = Easing, Color = Color? }
|
||||||
local function initializeErrorHandler(errorHandler)
|
function Animation.init(deps)
|
||||||
ErrorHandler = errorHandler
|
if type(deps) == "table" then
|
||||||
|
ErrorHandler = deps.ErrorHandler
|
||||||
|
Easing = deps.Easing
|
||||||
|
if deps.Color then
|
||||||
|
Color = deps.Color
|
||||||
|
Animation._ColorModule = deps.Color
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Export ErrorHandler initializer
|
-- Static method for Color module injection (for per-instance Color override)
|
||||||
Animation.initializeErrorHandler = initializeErrorHandler
|
function Animation.setColorModule(ColorModule)
|
||||||
|
Animation._ColorModule = ColorModule
|
||||||
|
end
|
||||||
|
|
||||||
return Animation
|
return Animation
|
||||||
|
|||||||
@@ -327,13 +327,12 @@ function AnimationGroup:apply(element)
|
|||||||
element.animationGroup = self
|
element.animationGroup = self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Initialize ErrorHandler dependency
|
--- Initialize dependencies
|
||||||
---@param errorHandler table The ErrorHandler module
|
---@param deps table Dependencies: { ErrorHandler = ErrorHandler }
|
||||||
local function initializeErrorHandler(errorHandler)
|
function AnimationGroup.init(deps)
|
||||||
ErrorHandler = errorHandler
|
if type(deps) == "table" then
|
||||||
|
ErrorHandler = deps.ErrorHandler
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Export ErrorHandler initializer
|
|
||||||
AnimationGroup.initializeErrorHandler = initializeErrorHandler
|
|
||||||
|
|
||||||
return AnimationGroup
|
return AnimationGroup
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
local ErrorHandler = nil
|
local ErrorHandler = nil
|
||||||
|
|
||||||
--- Initialize ErrorHandler dependency
|
|
||||||
---@param errorHandler table The ErrorHandler module
|
|
||||||
local function initializeErrorHandler(errorHandler)
|
|
||||||
ErrorHandler = errorHandler
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Standardized error message formatter (fallback for when ErrorHandler not available)
|
--- Standardized error message formatter (fallback for when ErrorHandler not available)
|
||||||
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
||||||
---@param message string
|
---@param message string
|
||||||
@@ -461,7 +455,12 @@ function Color.lerp(colorA, colorB, t)
|
|||||||
return Color.new(r, g, b, a)
|
return Color.new(r, g, b, a)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Export ErrorHandler initializer
|
--- Initialize dependencies
|
||||||
Color.initializeErrorHandler = initializeErrorHandler
|
---@param deps table Dependencies: { ErrorHandler = ErrorHandler }
|
||||||
|
function Color.init(deps)
|
||||||
|
if type(deps) == "table" then
|
||||||
|
ErrorHandler = deps.ErrorHandler
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return Color
|
return Color
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ local ErrorHandler = nil
|
|||||||
|
|
||||||
--- Initialize Units module with Context dependency
|
--- Initialize Units module with Context dependency
|
||||||
---@param context table The Context module
|
---@param context table The Context module
|
||||||
function Units.initialize(context)
|
--- Initialize dependencies
|
||||||
Context = context
|
---@param deps table Dependencies: { Context = Context?, ErrorHandler = ErrorHandler? }
|
||||||
end
|
function Units.init(deps)
|
||||||
|
if type(deps) == "table" then
|
||||||
--- Initialize ErrorHandler dependency
|
if deps.Context then
|
||||||
---@param errorHandler table The ErrorHandler module
|
Context = deps.Context
|
||||||
function Units.initializeErrorHandler(errorHandler)
|
end
|
||||||
ErrorHandler = errorHandler
|
if deps.ErrorHandler then
|
||||||
|
ErrorHandler = deps.ErrorHandler
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param value string|number
|
---@param value string|number
|
||||||
|
|||||||
@@ -300,10 +300,12 @@ end
|
|||||||
-- Validation utilities
|
-- Validation utilities
|
||||||
local ErrorHandler = nil
|
local ErrorHandler = nil
|
||||||
|
|
||||||
--- Initialize ErrorHandler dependency for validation utilities
|
--- Initialize dependencies
|
||||||
---@param errorHandler table The ErrorHandler module
|
---@param deps table Dependencies: { ErrorHandler = ErrorHandler }
|
||||||
local function initializeErrorHandler(errorHandler)
|
local function init(deps)
|
||||||
ErrorHandler = errorHandler
|
if type(deps) == "table" then
|
||||||
|
ErrorHandler = deps.ErrorHandler
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Validate that a value is in an enum table
|
--- Validate that a value is in an enum table
|
||||||
@@ -1104,7 +1106,7 @@ return {
|
|||||||
resolveTextSizePreset = resolveTextSizePreset,
|
resolveTextSizePreset = resolveTextSizePreset,
|
||||||
getModifiers = getModifiers,
|
getModifiers = getModifiers,
|
||||||
TEXT_SIZE_PRESETS = TEXT_SIZE_PRESETS,
|
TEXT_SIZE_PRESETS = TEXT_SIZE_PRESETS,
|
||||||
initializeErrorHandler = initializeErrorHandler,
|
init = init,
|
||||||
validateEnum = validateEnum,
|
validateEnum = validateEnum,
|
||||||
validateRange = validateRange,
|
validateRange = validateRange,
|
||||||
validateType = validateType,
|
validateType = validateType,
|
||||||
|
|||||||
@@ -2,18 +2,16 @@ local luaunit = require("testing.luaunit")
|
|||||||
require("testing.loveStub")
|
require("testing.loveStub")
|
||||||
|
|
||||||
local Animation = require("modules.Animation")
|
local Animation = require("modules.Animation")
|
||||||
|
local Easing = require("modules.Easing")
|
||||||
local Color = require("modules.Color")
|
local Color = require("modules.Color")
|
||||||
local Transform = require("modules.Transform")
|
local Transform = require("modules.Transform")
|
||||||
local ErrorHandler = require("modules.ErrorHandler")
|
local ErrorHandler = require("modules.ErrorHandler")
|
||||||
local ErrorCodes = require("modules.ErrorCodes")
|
local ErrorCodes = require("modules.ErrorCodes")
|
||||||
|
|
||||||
-- Initialize ErrorHandler
|
-- Initialize modules
|
||||||
ErrorHandler.init({ ErrorCodes = ErrorCodes })
|
ErrorHandler.init({ ErrorCodes = ErrorCodes })
|
||||||
Animation.initializeErrorHandler(ErrorHandler)
|
Color.init({ ErrorHandler = ErrorHandler })
|
||||||
Color.initializeErrorHandler(ErrorHandler)
|
Animation.init({ ErrorHandler = ErrorHandler, Easing = Easing, Color = Color })
|
||||||
|
|
||||||
-- Make Color module available to Animation
|
|
||||||
Animation.setColorModule(Color)
|
|
||||||
|
|
||||||
TestAnimationProperties = {}
|
TestAnimationProperties = {}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ local luaunit = require("testing.luaunit")
|
|||||||
require("testing.loveStub")
|
require("testing.loveStub")
|
||||||
|
|
||||||
local Animation = require("modules.Animation")
|
local Animation = require("modules.Animation")
|
||||||
|
local Easing = require("modules.Easing")
|
||||||
local ErrorHandler = require("modules.ErrorHandler")
|
local ErrorHandler = require("modules.ErrorHandler")
|
||||||
local ErrorCodes = require("modules.ErrorCodes")
|
local ErrorCodes = require("modules.ErrorCodes")
|
||||||
|
|
||||||
-- Initialize ErrorHandler for Animation module
|
-- Initialize modules
|
||||||
ErrorHandler.init({ ErrorCodes = ErrorCodes })
|
ErrorHandler.init({ ErrorCodes = ErrorCodes })
|
||||||
Animation.initializeErrorHandler(ErrorHandler)
|
Animation.init({ ErrorHandler = ErrorHandler, Easing = Easing })
|
||||||
|
|
||||||
TestAnimation = {}
|
TestAnimation = {}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ local luaunit = require("testing.luaunit")
|
|||||||
require("testing.loveStub")
|
require("testing.loveStub")
|
||||||
|
|
||||||
local Animation = require("modules.Animation")
|
local Animation = require("modules.Animation")
|
||||||
|
local Easing = require("modules.Easing")
|
||||||
local ErrorHandler = require("modules.ErrorHandler")
|
local ErrorHandler = require("modules.ErrorHandler")
|
||||||
local ErrorCodes = require("modules.ErrorCodes")
|
local ErrorCodes = require("modules.ErrorCodes")
|
||||||
|
|
||||||
-- Initialize ErrorHandler for Animation module
|
-- Initialize modules
|
||||||
ErrorHandler.init({ ErrorCodes = ErrorCodes })
|
ErrorHandler.init({ ErrorCodes = ErrorCodes })
|
||||||
Animation.initializeErrorHandler(ErrorHandler)
|
Animation.init({ ErrorHandler = ErrorHandler, Easing = Easing })
|
||||||
|
|
||||||
TestKeyframeAnimation = {}
|
TestKeyframeAnimation = {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user