import 'CoreLibs/easing' playdate.frameTimer = {} local frameTimers = {} local timersToRemove = {} -- metatable playdate.frameTimer.__index = function(table, key) if key == "frame" then return table._frame elseif key == "framesLeft" then return table.duration - table._frame elseif key == "startValue" then return table._startValue elseif key == "endValue" then return table._endValue elseif key == "easingFunction" then return table._easingFunction else return rawget(playdate.frameTimer, key) end end playdate.frameTimer.__newindex = function(table, key, value) if key == "frame" or key == "framesLeft" then -- read-only variables print("ERROR: playdate.frameTimer."..key.." is read-only.") elseif key == "startValue" then table._startValue = value table.originalValues.startValue = value elseif key == "endValue" then table._endValue = value table.originalValues.endValue = value elseif key == "easingFunction" then table._easingFunction = value table.originalValues.easingFunction = value else rawset(table, key, value) end end -- duration is in frames -- function playdate.frameTimer.new(duration, startValue, endValue, easingFunction) -- function.frameTimer.new(duration, f, args), where f is the timer ended callback, args are passed to the callback function playdate.frameTimer.new(duration, startValue, ...) assert(duration~=playdate.frameTimer, 'Please use playdate.frameTimer.new() instead of playdate.frameTimer:new()') local f = nil local args = nil local endValue = nil local easingFunction = nil if type(startValue) == "function" then -- playdate.frameTimer.new(duration, f, args) f = startValue if select("#", ...) > 0 then args = table.pack(...) end startValue = nil else endValue, easingFunction = select(1, ...) end local o = {} setmetatable(o, playdate.frameTimer) if not duration then print("playdate.frameTimer.new() requires a duration"); return nil end o.duration = duration o._startValue = startValue or 0 o._endValue = endValue or 0 o._frame = 0 o.value = o._startValue o.active = true o.delay = 0 o.remainingDelay = nil o.paused = false o.reverses = false o.hasReversed = false o.reverseEasingFunction = nil o.repeats = false o.discardOnCompletion = true o._easingFunction = easingFunction or playdate.easingFunctions.linear o.easingAmplitude = nil o.easingPeriod = nil o.updateCallback = nil o.timerEndedCallback = f or nil o.timerEndedArgs = args or nil o.attributes = {} o.originalValues = {} o.originalValues.startValue = o._startValue o.originalValues.endValue = o._endValue o.originalValues.easingFunction = o._easingFunction frameTimers[#frameTimers+1] = o return o end -- FrameTimer:performAfterDelay(delay, func, [...]) -- params: -- delay: frames until the function is fired -- func: the function to be called when the timer fires -- ...: optional list of parameters to func function playdate.frameTimer.performAfterDelay(delay, func, ...) local timer = playdate.frameTimer.new(delay) timer.timerEndedCallback = func if select("#", ...) > 0 then timer.timerEndedArgs = table.pack(...) end return timer end function playdate.frameTimer:pause() self.paused = true end function playdate.frameTimer:start() self.paused = false end function playdate.frameTimer:reset() self._startValue = self.originalValues.startValue self._endValue = self.originalValues.endValue self._easingFunction = self.originalValues.easingFunction self._frame = 0 self.active = true self.hasReversed = false self.remainingDelay = self.delay self.value = self._startValue end function playdate.frameTimer:remove() self.active = false self.discardOnCompletion = false -- in case this is being called from within the timer ended callback, don't remove the timer twice for i = 1, #frameTimers do if frameTimers[i] == self then timersToRemove[#timersToRemove+1] = self break end end end function playdate.frameTimer.updateTimers() local function updateActiveTimer(timer) if timer._startValue ~= timer._endValue and timer.duration ~= 0 then timer.value = timer._easingFunction(timer._frame, timer._startValue, timer._endValue - timer._startValue, timer.duration, timer.easingAmplitude, timer.easingPeriod) else timer.value = timer._endValue end if timer.updateCallback ~= nil then timer.updateCallback(timer) end end for i = 1, #frameTimers do local timer = frameTimers[i] if timer.active == true and timer.paused == false then if timer.remainingDelay == nil then -- doing it this way so delay can be set after the timer is created without having to call reset() afterwards timer.remainingDelay = timer.delay end if timer.remainingDelay > 0 then timer.remainingDelay -= 1 else timer._frame += 1 if timer._frame > timer.duration then local function callEndedCallback() if timer.timerEndedCallback ~= nil then if timer.timerEndedArgs == nil then timer.timerEndedCallback(timer) else timer.timerEndedCallback(table.unpack(timer.timerEndedArgs)) end end end if timer.reverses and timer.hasReversed == false then local temp = timer._startValue timer._startValue = timer._endValue timer._endValue = temp timer._frame = 0 timer.remainingDelay = timer.delay if timer.reverseEasingFunction ~= nil then timer._easingFunction = timer.reverseEasingFunction end timer.hasReversed = true -- so we don't reverse a second time (set repeats = true to do that) elseif timer.repeats then callEndedCallback() timer:reset() updateActiveTimer(timer) else timer.active = false timer._frame = timer._frame - 1 -- end up on the max frame instead of one past if timer.discardOnCompletion == true then -- remove the timer timersToRemove[#timersToRemove+1] = timer end callEndedCallback() end else -- timer is active updateActiveTimer(timer) end end end -- active timer end for _, timer in pairs( timersToRemove ) do local timerIndex = table.indexOfElement( frameTimers, timer ) if timerIndex then table.remove( frameTimers, timerIndex ) end end timersToRemove = {} end function playdate.frameTimer.allTimers() return frameTimers end