2013/Libraries/Fusion/Animation/Tween.luau

127 lines
3.5 KiB
Plaintext

--!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<T>(
goalState: PubTypes.StateObject<PubTypes.Animatable>,
tweenInfo: PubTypes.CanBeState<TweenInfo>?
): Types.Tween<T>
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