127 lines
3.5 KiB
Plaintext
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
|