--!nonstrict --[[ Constructs a new computed state object, which follows the value of another state object using a tween. ]] local PubTypes = require "../PubTypes" local External = require "../External" local Types = require "../Types" local TweenScheduler = require "../Animation/TweenScheduler" local logError = require "../Logging/logError" local logErrorNonFatal = require "../Logging/logErrorNonFatal" local xtypeof = require "../Utility/xtypeof" local peek = require "../State/peek" local typeof = require "../../../Modules/Polyfill/typeof" local class = {} local CLASS_METATABLE = { __index = class } local WEAK_KEYS_METATABLE = { __mode = "k" } --[[ Called when the goal state changes value; this will initiate a new tween. Returns false as the current value doesn't change right away. ]] function class:update(): boolean local goalValue = peek(self._goalState) -- if the goal hasn't changed, then this is a TweenInfo change. -- in that case, if we're not currently animating, we can skip everything if goalValue == self._nextValue and not self._currentlyAnimating then return false end local tweenInfo = peek(self._tweenInfo) -- if we receive a bad TweenInfo, then error and stop the update if typeof(tweenInfo) ~= "TweenInfo" then logErrorNonFatal("mistypedTweenInfo", nil, typeof(tweenInfo)) return false end self._prevValue = self._currentValue self._nextValue = goalValue self._currentTweenStartTime = External.lastUpdateStep() self._currentTweenInfo = tweenInfo local tweenDuration = tweenInfo.DelayTime + tweenInfo.Time if tweenInfo.Reverses then tweenDuration += tweenInfo.Time end tweenDuration *= tweenInfo.RepeatCount + 1 self._currentTweenDuration = tweenDuration -- start animating this tween TweenScheduler.add(self) return false end --[[ Returns the interior value of this state object. ]] function class:_peek(): any return self._currentValue end function class:get() logError "stateGetWasRemoved" end local function Tween( goalState: PubTypes.StateObject, tweenInfo: PubTypes.CanBeState? ): Types.Tween local currentValue = peek(goalState) -- apply defaults for tween info if tweenInfo == nil then tweenInfo = TweenInfo.new() end local dependencySet = { [goalState] = true } local tweenInfoIsState = xtypeof(tweenInfo) == "State" if tweenInfoIsState then dependencySet[tweenInfo] = true end local startingTweenInfo = peek(tweenInfo) -- If we start with a bad TweenInfo, then we don't want to construct a Tween if typeof(startingTweenInfo) ~= "TweenInfo" then logError("mistypedTweenInfo", nil, typeof(startingTweenInfo)) end local self = setmetatable({ type = "State", kind = "Tween", dependencySet = dependencySet, -- if we held strong references to the dependents, then they wouldn't be -- able to get garbage collected when they fall out of scope dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), _goalState = goalState, _tweenInfo = tweenInfo, _tweenInfoIsState = tweenInfoIsState, _prevValue = currentValue, _nextValue = currentValue, _currentValue = currentValue, -- store current tween into separately from 'real' tween into, so it -- isn't affected by :setTweenInfo() until next change _currentTweenInfo = tweenInfo, _currentTweenDuration = 0, _currentTweenStartTime = 0, _currentlyAnimating = false, }, CLASS_METATABLE) -- add this object to the goal state's dependent set goalState.dependentSet[self] = true return self end return Tween