From e3b34f988c684ff5c5043e3d66e4e6688f669ec2 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Tue, 30 May 2023 01:43:33 -0400 Subject: [PATCH 01/22] rewrite react-flow --- src/Animation.lua | 78 ---------- src/Animations/Base.lua | 14 ++ src/Animations/Types/Spring.lua | 99 ++++++++++++ src/Animations/Types/Tween.lua | 138 ++++++++++++++++ src/Animations/init.lua | 4 + src/Hooks/useAnimation.lua | 90 +++++++++++ src/Hooks/useGroupAnimation.lua | 107 +++++++++++++ src/Hooks/useLatest.lua | 18 --- src/Hooks/useSequence.lua | 55 ------- src/Hooks/useSequenceAnimation.lua | 120 ++++++++++++++ src/Hooks/useSpring.lua | 75 --------- src/Hooks/useStorage.lua | 19 --- src/Hooks/useTween.lua | 89 ----------- src/init.lua | 12 +- test/Test.lua | 242 ++++++++++++++++------------- 15 files changed, 709 insertions(+), 451 deletions(-) delete mode 100644 src/Animation.lua create mode 100644 src/Animations/Base.lua create mode 100644 src/Animations/Types/Spring.lua create mode 100644 src/Animations/Types/Tween.lua create mode 100644 src/Animations/init.lua create mode 100644 src/Hooks/useAnimation.lua create mode 100644 src/Hooks/useGroupAnimation.lua delete mode 100644 src/Hooks/useLatest.lua delete mode 100644 src/Hooks/useSequence.lua create mode 100644 src/Hooks/useSequenceAnimation.lua delete mode 100644 src/Hooks/useSpring.lua delete mode 100644 src/Hooks/useStorage.lua delete mode 100644 src/Hooks/useTween.lua diff --git a/src/Animation.lua b/src/Animation.lua deleted file mode 100644 index fbd85cf..0000000 --- a/src/Animation.lua +++ /dev/null @@ -1,78 +0,0 @@ -local Hooks = script.Parent.Hooks - -local useTween = require(Hooks.useTween) -local useSpring = require(Hooks.useSpring) - -export type SpringProps = { - start: number, - goal: number, - speed: number, - damper: number, -} - -export type TweenProps = { - start: number, - goal: number, - tweenInfo: TweenInfo, -} - -local function Animation(name: string, timeStep: number?, props: SpringProps | TweenProps) - local update - local value - - local play, playReverse, stop - - if name == "Tween" then - assert(props.start, "Tween requires a start value") - assert(props.goal, "Tween requires a goal value") - assert(props.tweenInfo, "Tween requires a tweenInfo") - - value, update, stop = useTween(props.start, props.tweenInfo) - play = function() - update({ - start = props.start, - goal = props.goal, - }) - end - - playReverse = function() - update({ - start = props.goal, - goal = props.start, - }) - end - elseif name == "Spring" then - assert(props.start, "Spring requires a start value") - assert(props.goal, "Spring requires a goal value") - - value, update, stop = useSpring(props.start, props.speed, props.damper) - play = function() - update({ - value = props.start, - goal = props.goal, - force = props.force, - }) - end - - playReverse = function() - update({ - value = props.goal, - goal = props.start, - force = props.force, - }) - end - else - assert(false, name .. " is not a valid animation type") - end - - return { - timeStep = timeStep, - start = props.start, - value = value, - playReverse = playReverse, - play = play, - stop = stop, - } -end - -return Animation diff --git a/src/Animations/Base.lua b/src/Animations/Base.lua new file mode 100644 index 0000000..43e06be --- /dev/null +++ b/src/Animations/Base.lua @@ -0,0 +1,14 @@ +local BaseAnimation = {} + +function BaseAnimation.new() + local self = {} + + self.listener = nil + self.SetListener = function(_, listener: (any) -> ()) + self.listener = listener + end + + return self +end + +return BaseAnimation diff --git a/src/Animations/Types/Spring.lua b/src/Animations/Types/Spring.lua new file mode 100644 index 0000000..aa0d828 --- /dev/null +++ b/src/Animations/Types/Spring.lua @@ -0,0 +1,99 @@ +local RunService = game:GetService("RunService") + +local BaseAnimation = require(script.Parent.Parent.Base) +local Promise = require(script.Parent.Parent.Parent.Promise) +local SpringValue = require(script.Parent.Parent.Parent.Utility.SpringValue) + +local Spring = {} +Spring.__index = Spring + +export type Spring = typeof(Spring.new()) +export type SpringProperties = { + damper: number?, + speed: number?, + from: any, + to: any, +} + +local Springs = {} :: { + [Spring]: { + completed: () -> nil, + resolve: () -> (), + }, +} + +function Spring.new(props: SpringProperties) + local self = setmetatable(BaseAnimation.new(), Spring) :: Spring + + self.props = props + self.player = nil + + return self +end + +function Spring:Play(from: any?) + if self.playing then + self:Stop() + end + + local baseFromValue = from or self.props.start :: any + local baseToValue = self.props.target :: any + + assert(baseFromValue, "No from value provided") + assert(baseToValue, "No to value provided") + + if from == baseToValue then + return Promise.resolve() + end + + local animation = Promise.new(function(resolve, _, onCancel) + local newSpring = SpringValue.new(baseFromValue, self.props.speed, self.props.damper) + + newSpring:SetGoal(baseToValue) + Springs[newSpring] = { + resolve = resolve, + update = function() + if self.listener then + self.listener(newSpring:GetValue()) + end + end, + } + + onCancel(function() + Springs[newSpring] = nil + resolve() + end) + end) + + self.playing = true + self.player = animation + + return animation +end + +function Spring:Stop() + if not self.playing then + return + end + + if self.player then + self.player:cancel() + self.player = nil + end + + self.playing = false +end + +RunService:BindToRenderStep("UPDATE_REACT_SPRINGS", Enum.RenderPriority.First.Value, function(dt: number) + for spring, data in Springs do + local didUpdate = spring:Update(dt) + data.update() + + if not didUpdate then + Springs[spring] = nil + data.resolve() + end + end +end) + +return Spring diff --git a/src/Animations/Types/Tween.lua b/src/Animations/Types/Tween.lua new file mode 100644 index 0000000..01c22d0 --- /dev/null +++ b/src/Animations/Types/Tween.lua @@ -0,0 +1,138 @@ +local TweenService = game:GetService("TweenService") + +local BaseAnimation = require(script.Parent.Parent.Base) +local Promise = require(script.Parent.Parent.Parent.Promise) +local LinearValue = require(script.Parent.Parent.Parent.Utility.LinearValue) + +local Tween = {} +Tween.__index = Tween + +export type Tween = typeof(Tween.new()) +export type TweenProperties = { + from: any, + to: any, +} + +local function playTween(tweenInfo, callback: (number) -> nil, completed: () -> nil) + local numberValue = Instance.new("NumberValue") + numberValue.Value = 0 + numberValue:GetPropertyChangedSignal("Value"):Connect(function() + callback(numberValue.Value) + end) + + local tween = TweenService:Create(numberValue, tweenInfo, { + Value = 1, + }) + + tween.Completed:Connect(function() + numberValue:Destroy() + completed() + end) + + return function() + tween:Play() + end, function() + numberValue:Destroy() + tween:Cancel() + end +end + +function Tween.new(props: TweenProperties) + local self = setmetatable(BaseAnimation.new(), Tween) :: Tween + + self.props = props + self.player = nil + + return self +end + +function Tween:Play(from: any?) + if self.playing then + self:Stop() + end + + local tweenInfo = self.props.info :: TweenInfo + local baseFromValue = from or self.props.start :: any + local baseToValue = self.props.target :: any + + -- start immediately will start the tween but not update the listener + local startImmediately = self.props.immediate == true + local delayTime = self.props.delay :: number + + if not delayTime then + assert(startImmediately == false, "Cannot start immediately without a delay") + else + if startImmediately then + assert(delayTime < tweenInfo.Time, "DelayTime must be less than Time") + end + end + + assert(baseFromValue, "No from value provided") + assert(baseToValue, "No to value provided") + + assert(tweenInfo, "No tween info provided") + assert(tweenInfo.RepeatCount == 0, "RepeatCount must be 0") + assert(tweenInfo.Reverses == false, "Reverses must be false") + assert(tweenInfo.DelayTime == 0, "DelayTime must be 0") + + if from == baseToValue then + return Promise.resolve() + end + + local fromValue = LinearValue.fromValue(baseFromValue) + local toValue = LinearValue.fromValue(baseToValue) + + local animation = Promise.new(function(resolve, _, onCancel) + local canUpdate = true + + local play, cancel = playTween(tweenInfo, function(value) + if not canUpdate then + return + end + + local newValue = fromValue:Lerp(toValue, value):ToValue() + self.listener(newValue) + end, function() + self.playing = false + self.player = nil + resolve() + end) + + onCancel(cancel) + + if not delayTime then + play() + else + if startImmediately then + canUpdate = false + play() + + task.wait(delayTime) + canUpdate = true + else + task.wait(delayTime) + play() + end + end + end) + + self.playing = true + self.player = animation + + return animation +end + +function Tween:Stop() + if not self.playing then + return + end + + if self.player then + self.player:cancel() + self.player = nil + end + + self.playing = false +end + +return Tween diff --git a/src/Animations/init.lua b/src/Animations/init.lua new file mode 100644 index 0000000..2430dae --- /dev/null +++ b/src/Animations/init.lua @@ -0,0 +1,4 @@ +return { + Spring = require(script.Types.Spring).new, + Tween = require(script.Types.Tween).new, +} diff --git a/src/Hooks/useAnimation.lua b/src/Hooks/useAnimation.lua new file mode 100644 index 0000000..2b2bc7a --- /dev/null +++ b/src/Hooks/useAnimation.lua @@ -0,0 +1,90 @@ +local Promise = require(script.Parent.Parent.Promise) + +local Roact = require(script.Parent.Parent.Roact) +local useRef = Roact.useRef + +export type AnimationProps = { + [string]: any, +} + +local Animation = {} +Animation.__index = Animation + +function Animation.new(props: AnimationProps) + local self = setmetatable({}, Animation) + + self.playing = false + self.listener = nil + self.animation = props + + for name, animation in props do + animation:SetListener(function(value) + if self.listener then + self.listener(name, value) + end + end) + end + + return self +end + +function Animation:SetListener(listener: (string, any) -> ()) + self.listener = listener +end + +function Animation:Play(fromProps: AnimationProps) + if self.playing then + self:Stop() + end + + local animation = Promise.new(function(resolve, _, onCancel) + local promises = {} + + for name, animatable in self.animation do + local animationPromise = animatable:Play(fromProps[name]) + table.insert(promises, animationPromise) + end + + local awaiter = Promise.all(promises):andThen(resolve) + + onCancel(function() + awaiter:cancel() + + for _, promise in promises do + promise:cancel() + end + end) + end) + + self.playing = true + self.player = animation + + return animation +end + +function Animation:Stop() + if not self.playing then + return + end + + if self.player then + self.player:cancel() + self.player = nil + end + + self.playing = false +end + +local function useAnimation(props: AnimationProps) + local animation = useRef() + local current = animation.current + + if not current then + current = Animation.new(props) + animation.current = current + end + + return current +end + +return useAnimation diff --git a/src/Hooks/useGroupAnimation.lua b/src/Hooks/useGroupAnimation.lua new file mode 100644 index 0000000..3d046e8 --- /dev/null +++ b/src/Hooks/useGroupAnimation.lua @@ -0,0 +1,107 @@ +local Roact = require(script.Parent.Parent.Roact) +local useState = Roact.useState +local useRef = Roact.useRef + +local GroupAnimationController = {} +GroupAnimationController.__index = GroupAnimationController + +type StateSetters = { + [string]: (any) -> nil, +} + +export type Animation = { + Play: (DefaultProperties) -> Animation, + Stop: () -> nil, +} + +export type GroupAnimation = { + [string]: Animation, +} + +export type DefaultProperties = { + [string]: any, +} + +function GroupAnimationController.new(props: GroupAnimation, default: DefaultProperties, setters: StateSetters) + local self = setmetatable({}, GroupAnimationController) + + self.currentState = "Default" + self.animations = props + self.state = default + + for state, animation in props do + animation:SetListener(function(name, value) + if self.currentState ~= state then + -- warn("IGNORED UPDATE:", state, name, value) + return + end + + -- warn("UPDATE:", state, name, value) + self.state[name] = value + setters[name](value) + end) + end + + return self +end + +function GroupAnimationController:Play(newState: string) + local animation = self.animations[newState] + assert(animation, `No animation found for state {newState}`) + + self.currentState = newState + + if self.currentAnimation then + self.currentAnimation:Stop() + end + + self.currentAnimation = animation + animation:Play(self.state) +end + +function GroupAnimationController:Stop() + if self.currentAnimation then + self.currentAnimation:Stop() + self.currentAnimation = nil + end +end + +local function getStateContainer(defaults: DefaultProperties) + local setters = {} + local values = {} + + for name, value in defaults do + local state, setState = useState(value) + + setters[name] = setState + values[name] = state + end + + return setters, values +end + +local function useGroupAnimation(props: GroupAnimation, default: DefaultProperties) + local controller = useRef() + local current = controller.current + local setters, values = getStateContainer(default) + + if not current then + local newController = GroupAnimationController.new(props, default, setters) + + current = { + play = function(newState: string) + newController:Play(newState) + end, + + stop = function() + newController:Stop() + end, + } + + controller.current = current + end + + return values, current.play, current.stop +end + +return useGroupAnimation diff --git a/src/Hooks/useLatest.lua b/src/Hooks/useLatest.lua deleted file mode 100644 index 4f2dfb4..0000000 --- a/src/Hooks/useLatest.lua +++ /dev/null @@ -1,18 +0,0 @@ -local Roact = require(script.Parent.Parent.Roact) -local useEffect = Roact.useEffect -local useState = Roact.useState - -local function useLatest(...: any...) - local initialValue = select(1, ...) - local state, setState = useState(initialValue) - - for _, value in { ... } do - useEffect(function() - setState(value) - end, { value }) - end - - return state -end - -return useLatest diff --git a/src/Hooks/useSequence.lua b/src/Hooks/useSequence.lua deleted file mode 100644 index 7f27ed0..0000000 --- a/src/Hooks/useSequence.lua +++ /dev/null @@ -1,55 +0,0 @@ -local Roact = require(script.Parent.Parent.Roact) -local useRef = Roact.useRef - -local Animation = require(script.Parent.Parent.Animation) -export type Animation = typeof(Animation) - -local function makeSequence(animations: { [string]: Animation }) - local currentSequence - - return { - play = function(data: { reverse: boolean }?) - local reverse = data and data.reverse - local nonce = tick() - currentSequence = nonce - - for _, animation in animations do - animation.useStart.current = true - - task.delay(animation.timeStep or 0, function() - if currentSequence == nonce then - animation.useStart.current = false - animation[reverse and "playReverse" or "play"]() - end - end) - end - end, - - stop = function() - currentSequence = nil - for _, animation in animations do - animation.stop() - end - end, - } -end - -local function useSequence(animations: { [string]: Animation }) - local sequence = {} - local sequenceRef = useRef() - local currentSequence = sequenceRef.current - - if not currentSequence then - currentSequence = makeSequence(animations) - sequenceRef.current = currentSequence - end - - for name, animation in animations do - animation.useStart = useRef(true) - sequence[name] = animation.useStart.current and animation.start or animation.value - end - - return sequence, currentSequence.play, currentSequence.stop -end - -return useSequence diff --git a/src/Hooks/useSequenceAnimation.lua b/src/Hooks/useSequenceAnimation.lua new file mode 100644 index 0000000..191f60b --- /dev/null +++ b/src/Hooks/useSequenceAnimation.lua @@ -0,0 +1,120 @@ +local Promise = require(script.Parent.Parent.Promise) + +local Roact = require(script.Parent.Parent.Roact) +local useRef = Roact.useRef + +export type SequenceProps = { { timestamp: number } | any } + +local Sequence = {} +Sequence.__index = Sequence + +function Sequence.new(props: SequenceProps) + local self = setmetatable({}, Sequence) + + self.playing = false + self.listener = nil + self.animation = props + + table.sort(props, function(a, b) + return a.timestamp < b.timestamp + end) + + local lastTimestamp + + for _, animation in props do + local timestamp = animation.timestamp + if lastTimestamp == timestamp then + error("Duplicate timestamp found in sequence") + end + + lastTimestamp = timestamp + + for name, animatable in animation do + if name == "timestamp" then + continue + end + + animatable:SetListener(function(value) + if self.listener then + self.listener(name, value) + end + end) + end + end + + return self +end + +function Sequence:SetListener(listener: (string, any) -> ()) + self.listener = listener +end + +function Sequence:Play(fromProps: SequenceProps) + if self.playing then + self:Stop() + end + + local animation = Promise.new(function(resolve, _, onCancel) + local promises = {} + + for _, sequenced in self.animation do + local animationPromise = Promise.delay(sequenced.timestamp):andThen(function() + local allAnimatables = {} + + for name, animatable in sequenced do + if name == "timestamp" then + continue + end + + table.insert(allAnimatables, animatable:Play(fromProps[name])) + end + + return Promise.all(allAnimatables) + end) + + table.insert(promises, animationPromise) + end + + local awaiter = Promise.all(promises):andThen(resolve) + + onCancel(function() + for _, promise in promises do + promise:cancel() + end + + awaiter:cancel() + end) + end) + + self.playing = true + self.player = animation + + return animation +end + +function Sequence:Stop() + if not self.playing then + return + end + + if self.player then + self.player:cancel() + self.player = nil + end + + self.playing = false +end + +local function useSequenceAnimation(props: SequenceProps) + local animation = useRef() + local current = animation.current + + if not current then + current = Sequence.new(props) + animation.current = current + end + + return current +end + +return useSequenceAnimation diff --git a/src/Hooks/useSpring.lua b/src/Hooks/useSpring.lua deleted file mode 100644 index e71df6c..0000000 --- a/src/Hooks/useSpring.lua +++ /dev/null @@ -1,75 +0,0 @@ -local RunService = game:GetService("RunService") - -local Roact = require(script.Parent.Parent.Roact) -local useEffect = Roact.useEffect -local useState = Roact.useState -local useMemo = Roact.useMemo - -local Utility = script.Parent.Parent.Utility -local LinearValue = require(Utility.LinearValue) -local SpringValue = require(Utility.SpringValue) -local SpringValues = {} - -RunService:UnbindFromRenderStep("UPDATE_REACT_SPRING") -RunService:BindToRenderStep("UPDATE_REACT_SPRING", Enum.RenderPriority.First.Value, function(dt: number) - for spring, setValue in pairs(SpringValues) do - local didUpdate = spring:Update(dt) - local updatedValue = spring:GetValue() - - if not didUpdate then - SpringValues[spring] = nil - updatedValue = spring:GetGoal() - end - - setValue(updatedValue) - end -end) - -local function makeSpring( - updateState: (any) -> (), - initial: LinearValue.LinearValueType, - speed: number?, - dampening: number? -) - local springValue = SpringValue.new(initial, speed, dampening) - - return { - play = function(animation: { - goal: LinearValue.LinearValueType?, - value: LinearValue.LinearValueType?, - force: LinearValue.LinearValueType?, - }) - if animation.goal then - springValue:SetGoal(animation.goal) - end - - if animation.value then - springValue:SetValue(animation.value) - end - - if animation.force then - springValue:Impulse(animation.force) - end - - springValue:Run(updateState) - end, - - stop = function() - springValue:Stop() - end, - } -end - -local function useSpring(initial: LinearValue.LinearValueType, speed: number?, dampening: number?) - speed = speed or 20 - dampening = dampening or 1 - - local state, setState = useState(initial) - local spring = useMemo(function() - return makeSpring(setState, initial, speed, dampening) - end, { initial, speed, dampening }) - - return state, spring.play, spring.stop -end - -return useSpring diff --git a/src/Hooks/useStorage.lua b/src/Hooks/useStorage.lua deleted file mode 100644 index e47f713..0000000 --- a/src/Hooks/useStorage.lua +++ /dev/null @@ -1,19 +0,0 @@ -local Roact = require(script.Parent.Parent.Roact) -local useRef = Roact.useRef - -local function useStorage(name: string?) - name = name or "global" - - local globalStack = useRef({}) - - local currenStack = globalStack.current - local stack = currenStack[name] - if not stack then - stack = {} - currenStack[name] = stack - end - - return stack -end - -return useStorage diff --git a/src/Hooks/useTween.lua b/src/Hooks/useTween.lua deleted file mode 100644 index 5728ea1..0000000 --- a/src/Hooks/useTween.lua +++ /dev/null @@ -1,89 +0,0 @@ -local TweenService = game:GetService("TweenService") - -local Roact = require(script.Parent.Parent.Roact) -local useRef = Roact.useRef -local useState = Roact.useState -local useEffect = Roact.useEffect - -local LinearValue = require(script.Parent.Parent.Utility.LinearValue) -type LinearValue = typeof(LinearValue) - -local function makeTween(updateState: (any) -> (), initial: LinearValue.LinearValueType, tweenInfo: TweenInfo) - local start, goal, tween - local current = LinearValue.fromValue(initial) - - local value = Instance.new("NumberValue") - value:GetPropertyChangedSignal("Value"):Connect(function() - current = start:Lerp(goal, value.Value) - updateState(current:ToValue()) - end) - - return { - destroy = function() - value:Destroy() - end, - - play = function(animation: { - start: LinearValue.LinearValueType?, - goal: LinearValue.LinearValueType?, - }) - local startValue = animation.start - local endValue = animation.goal - - local currentValue = current:ToValue() - - if startValue == currentValue and endValue == currentValue then - return - end - - if not startValue then - startValue = currentValue - end - - if tween then - tween:Cancel() - end - - start = LinearValue.fromValue(startValue) - goal = LinearValue.fromValue(endValue) - - value.Value = 0 - - tween = TweenService:Create(value, tweenInfo, { Value = 1 }) - tween:Play() - end, - - stop = function() - if tween then - tween:Cancel() - tween = nil - end - end, - } -end - -local function useTween(initial: LinearValue.LinearValueType, tweenInfo: TweenInfo) - local current, setState = useState(initial) - - local tweenRef = useRef() - local currentTweenRef = tweenRef.current - - if not currentTweenRef then - currentTweenRef = makeTween(setState, initial, tweenInfo) - tweenRef.current = currentTweenRef - end - - useEffect(function() - return function() - local currentTween = tweenRef.current - if currentTween then - currentTween:destroy() - tweenRef.current = nil - end - end - end, {}) - - return current, currentTweenRef.play, currentTweenRef.stop -end - -return useTween diff --git a/src/init.lua b/src/init.lua index bb303a8..a06edcc 100644 --- a/src/init.lua +++ b/src/init.lua @@ -1,8 +1,10 @@ +local Animations = require(script.Animations) + return { - Animation = require(script.Animation), + Tween = Animations.Tween, + Spring = Animations.Spring, - useLatest = require(script.Hooks.useLatest), - useSequence = require(script.Hooks.useSequence), - useSpring = require(script.Hooks.useSpring), - useTween = require(script.Hooks.useTween), + useAnimation = require(script.Hooks.useAnimation), + useGroupAnimation = require(script.Hooks.useGroupAnimation), + useSequenceAnimation = require(script.Hooks.useSequenceAnimation), } diff --git a/test/Test.lua b/test/Test.lua index 28f0fa4..e55e0eb 100644 --- a/test/Test.lua +++ b/test/Test.lua @@ -6,20 +6,25 @@ local useEffect = Roact.useEffect local createElement = Roact.createElement local ReactAnimation = require(Packages.RoactAnimation) -local Animation = ReactAnimation.Animation -local useTween = ReactAnimation.useTween -local useSpring = ReactAnimation.useSpring -local useSequence = ReactAnimation.useSequence -local useLatest = ReactAnimation.useLatest +local useGroupAnimation = ReactAnimation.useGroupAnimation +local useSequenceAnimation = ReactAnimation.useSequenceAnimation +local useAnimation = ReactAnimation.useAnimation +-- local Spring = ReactAnimation.Spring +local Tween = ReactAnimation.Tween -- @helpers +local RNG = Random.new() + local function createFrame(props, children) props.Size = props.Size or UDim2.fromScale(1, 1) props.SizeConstraint = Enum.SizeConstraint.RelativeXX props.Text = props.Name + local parentSize = props.ParentSize or UDim2.fromScale(0.1, 0.25) + props.ParentSize = nil + return createElement("Frame", { - Size = UDim2.fromScale(0.1, 0.25), + Size = parentSize, AnchorPoint = Vector2.new(0.5, 0), BackgroundTransparency = 1, }, { @@ -30,129 +35,143 @@ end -- @tests -- @TestSequence local function TestSequence() - local sequence, play, stop = useSequence({ - position = Animation( - "Spring", - 0, - { start = UDim2.fromScale(-1, 0), goal = UDim2.fromScale(1, 0), speed = 20, damper = 0.7 } - ), - - size = Animation( - "Tween", - 0.2, - { start = UDim2.fromScale(0.5, 0.5), goal = UDim2.fromScale(1.2, 1.2), tweenInfo = TweenInfo.new(1) } - ), - }) - - useEffect(function() - play() - - return stop - end, {}) - - return createFrame({ - Name = "Sequence", - Position = sequence.position, - Size = sequence.size, - }) -end - --- @TestPriority -local function TestPriority() - local sequence1, play1 = useSequence({ - position = Animation( - "Spring", - 0, - { start = UDim2.fromScale(-1, 0), goal = UDim2.fromScale(1, 0), speed = 20, damper = 0.7 } - ), - - size = Animation( - "Tween", - 1, - { start = UDim2.fromScale(0.5, 0.5), goal = UDim2.fromScale(1.2, 1.2), tweenInfo = TweenInfo.new(1) } - ), - }) - - local sequence2, play2 = useSequence({ - position = Animation( - "Spring", - 0.1, - { start = UDim2.fromScale(1, 0), goal = UDim2.fromScale(-1, 0), speed = 20, damper = 0.7 } - ), - - size = Animation( - "Tween", - 0.2, - { start = UDim2.fromScale(0.7, 0.7), goal = UDim2.fromScale(0.1, 0.1), tweenInfo = TweenInfo.new(1) } - ), - }) - - useEffect(function() - play1() - play2() - end, {}) - - return createFrame({ - Name = "Priority", - Position = useLatest(sequence1.position, sequence2.position), - Size = useLatest(sequence1.size, sequence2.size), - }) -end - --- @TestTween -local function TestTween() - local value, update = useTween(UDim2.fromScale(0, 0), TweenInfo.new(1)) - local value2, update2 = useTween(Color3.new(), TweenInfo.new(1)) - - if value.X.Scale == 0 then - update({ goal = UDim2.fromScale(1, 0) }) - elseif (value :: UDim2).X.Scale == 1 then - update({ goal = UDim2.fromScale(0, 0) }) - end - - if value2.R == 0 then - update2({ start = Color3.new(1, 0, 0), goal = Color3.new(1, 0, 0) }) - elseif value2.R == 1 then - update2({ goal = Color3.new(0, 0, 0) }) - end - - return createFrame({ - Name = "Tween", - Position = value, - BackgroundColor3 = value2, + -- local sequence, play, stop = useGroupAnimation({ + -- moveRight = useAnimation({ + -- -- position = Spring({ + -- -- target = UDim2.fromScale(0.8, 0), + -- -- speed = 5, + -- -- damper = 0.7, + -- -- }), + + -- position = Tween({ + -- target = UDim2.fromScale(0.8, 0), + -- info = TweenInfo.new(1, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut), + -- }), + -- }), + + -- moveLeft = useAnimation({ + -- -- position = Spring({ + -- -- target = UDim2.fromScale(0.3, 0), + -- -- speed = 5, + -- -- damper = 0.7, + -- -- }), + + -- position = Tween({ + -- target = UDim2.fromScale(0.3, 0), + -- info = TweenInfo.new(1, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut), + -- }), + -- }), + -- }, { + -- position = UDim2.fromScale(0.5, 0), + -- }) + + local sequence, play, stop = useGroupAnimation({ + yo = useSequenceAnimation({ + { + timestamp = 0, + size = Tween({ + target = UDim2.fromOffset(400, 400), + info = TweenInfo.new(1, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut), + }), + }, + { + timestamp = 1, + position = Tween({ + target = UDim2.fromScale(0.3, 0), + info = TweenInfo.new(1, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut), + }), + }, + { + timestamp = 2, + position = Tween({ + target = UDim2.fromScale(0.8, 0), + info = TweenInfo.new(1, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut), + }), + }, + }), + }, { + position = UDim2.fromScale(0.5, 0), + size = UDim2.fromOffset(200, 200), }) -end - --- @TestSpring -local function TestSpring() - local value, update = useSpring(UDim2.fromScale(-1, 0), 10, 0.5) useEffect(function() local running = true task.spawn(function() while running do - update({ goal = UDim2.fromScale(1, 0) }) + play("yo") task.wait(3) - - if running then - update({ goal = UDim2.fromScale(-1, 0) }) - task.wait(3) - end end end) return function() running = false + stop() end end, {}) return createFrame({ - Name = "Spring", - Position = value, + Name = "Sequence", + Position = sequence.position, + Size = sequence.size, + ParentSize = UDim2.fromScale(1, 0.25), }) end +-- -- @TestTween +-- local function TestTween() +-- local value, update = useTween(UDim2.fromScale(0, 0), TweenInfo.new(1)) +-- local value2, update2 = useTween(Color3.new(), TweenInfo.new(1)) + +-- if value.X.Scale == 0 then +-- update({ goal = UDim2.fromScale(1, 0) }) +-- elseif (value :: UDim2).X.Scale == 1 then +-- update({ goal = UDim2.fromScale(0, 0) }) +-- end + +-- if value2.R == 0 then +-- update2({ start = Color3.new(1, 0, 0), goal = Color3.new(1, 0, 0) }) +-- elseif value2.R == 1 then +-- update2({ goal = Color3.new(0, 0, 0) }) +-- end + +-- return createFrame({ +-- Name = "Tween", +-- Position = value, +-- BackgroundColor3 = value2, +-- }) +-- end + +-- -- @TestSpring +-- local function TestSpring() +-- local value, update = useSpring(UDim2.fromScale(-1, 0), 10, 0.5) + +-- useEffect(function() +-- local running = true + +-- task.spawn(function() +-- while running do +-- update({ goal = UDim2.fromScale(1, 0) }) +-- task.wait(3) + +-- if running then +-- update({ goal = UDim2.fromScale(-1, 0) }) +-- task.wait(3) +-- end +-- end +-- end) + +-- return function() +-- running = false +-- end +-- end, {}) + +-- return createFrame({ +-- Name = "Spring", +-- Position = value, +-- }) +-- end + -- @entry local function Test() return createElement("Frame", { @@ -164,10 +183,9 @@ local function Test() VerticalAlignment = Enum.VerticalAlignment.Center, }), - tween = createElement(TestTween), - spring = createElement(TestSpring), + -- tween = createElement(TestTween), + -- spring = createElement(TestSpring), sequence = createElement(TestSequence), - priority = createElement(TestPriority), }) end From b5acc6cb663bf60e956ccd3609afe0ef08018465 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Tue, 30 May 2023 01:46:01 -0400 Subject: [PATCH 02/22] Update roact->react --- README.md | 2 +- default.project.json | 2 +- src/Hooks/useAnimation.lua | 4 ++-- src/Hooks/useGroupAnimation.lua | 6 +++--- src/Hooks/useSequenceAnimation.lua | 4 ++-- src/React.lua | 1 + src/Roact.lua | 1 - test.project.json | 4 ++-- test/Test.lua | 8 ++++---- test/init.client.lua | 8 ++++---- test/init.story.lua | 12 ++++++------ wally.toml | 4 ++-- 12 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 src/React.lua delete mode 100644 src/Roact.lua diff --git a/README.md b/README.md index cc7e567..e3c7b14 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# Roact-Flow +# React-Flow diff --git a/default.project.json b/default.project.json index 9d964fb..3ab210f 100644 --- a/default.project.json +++ b/default.project.json @@ -1,5 +1,5 @@ { - "name": "roact-flow", + "name": "react-flow", "tree": { "$path": "src" } diff --git a/src/Hooks/useAnimation.lua b/src/Hooks/useAnimation.lua index 2b2bc7a..cc2d46c 100644 --- a/src/Hooks/useAnimation.lua +++ b/src/Hooks/useAnimation.lua @@ -1,7 +1,7 @@ local Promise = require(script.Parent.Parent.Promise) -local Roact = require(script.Parent.Parent.Roact) -local useRef = Roact.useRef +local React = require(script.Parent.Parent.React) +local useRef = React.useRef export type AnimationProps = { [string]: any, diff --git a/src/Hooks/useGroupAnimation.lua b/src/Hooks/useGroupAnimation.lua index 3d046e8..2db5e64 100644 --- a/src/Hooks/useGroupAnimation.lua +++ b/src/Hooks/useGroupAnimation.lua @@ -1,6 +1,6 @@ -local Roact = require(script.Parent.Parent.Roact) -local useState = Roact.useState -local useRef = Roact.useRef +local React = require(script.Parent.Parent.React) +local useState = React.useState +local useRef = React.useRef local GroupAnimationController = {} GroupAnimationController.__index = GroupAnimationController diff --git a/src/Hooks/useSequenceAnimation.lua b/src/Hooks/useSequenceAnimation.lua index 191f60b..532fd2f 100644 --- a/src/Hooks/useSequenceAnimation.lua +++ b/src/Hooks/useSequenceAnimation.lua @@ -1,7 +1,7 @@ local Promise = require(script.Parent.Parent.Promise) -local Roact = require(script.Parent.Parent.Roact) -local useRef = Roact.useRef +local React = require(script.Parent.Parent.React) +local useRef = React.useRef export type SequenceProps = { { timestamp: number } | any } diff --git a/src/React.lua b/src/React.lua new file mode 100644 index 0000000..45ac990 --- /dev/null +++ b/src/React.lua @@ -0,0 +1 @@ +return require(script.Parent.Parent.React) diff --git a/src/Roact.lua b/src/Roact.lua deleted file mode 100644 index e189fb4..0000000 --- a/src/Roact.lua +++ /dev/null @@ -1 +0,0 @@ -return require(script.Parent.Parent.Roact) diff --git a/test.project.json b/test.project.json index da44991..7bcd5be 100644 --- a/test.project.json +++ b/test.project.json @@ -1,5 +1,5 @@ { - "name": "Roact-Flow Animation Test", + "name": "React-Flow Animation Test", "tree": { "$className": "DataModel", "ReplicatedStorage": { @@ -9,7 +9,7 @@ "$path": "Packages" }, - "RoactAnimation": { + "ReactAnimation": { "$path": "src" } }, diff --git a/test/Test.lua b/test/Test.lua index e55e0eb..69a41e2 100644 --- a/test/Test.lua +++ b/test/Test.lua @@ -1,11 +1,11 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") local Packages = ReplicatedStorage:WaitForChild("Packages") -local Roact = require(Packages.Roact) -local useEffect = Roact.useEffect -local createElement = Roact.createElement +local React = require(Packages.React) +local useEffect = React.useEffect +local createElement = React.createElement -local ReactAnimation = require(Packages.RoactAnimation) +local ReactAnimation = require(Packages.ReactAnimation) local useGroupAnimation = ReactAnimation.useGroupAnimation local useSequenceAnimation = ReactAnimation.useSequenceAnimation local useAnimation = ReactAnimation.useAnimation diff --git a/test/init.client.lua b/test/init.client.lua index 13ceb54..59a2726 100644 --- a/test/init.client.lua +++ b/test/init.client.lua @@ -3,13 +3,13 @@ local Players = game:GetService("Players") local LocalPlayer = Players.LocalPlayer local Packages = ReplicatedStorage:WaitForChild("Packages") -ReplicatedStorage.RoactAnimation.Parent = Packages +ReplicatedStorage.ReactAnimation.Parent = Packages local Test = require(script.Test) -local Roact = require(Packages.Roact) -local createElement = Roact.createElement -local mount = Roact.mount +local React = require(Packages.React) +local createElement = React.createElement +local mount = React.mount if not LocalPlayer.Character then LocalPlayer.CharacterAdded:Wait() diff --git a/test/init.story.lua b/test/init.story.lua index ccb5cde..7c54d5a 100644 --- a/test/init.story.lua +++ b/test/init.story.lua @@ -1,19 +1,19 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") local Packages = ReplicatedStorage.Packages -local Roact = require(Packages.Roact) -local createElement = Roact.createElement -local mount, unmount = Roact.mount, Roact.unmount +local React = require(Packages.React) +local createElement = React.createElement +local mount, unmount = React.mount, React.unmount return function(container) - local roactAnimation = ReplicatedStorage.RoactAnimation - roactAnimation.Parent = Packages + local ReactAnimation = ReplicatedStorage.ReactAnimation + ReactAnimation.Parent = Packages local Test = require(script.Parent.Test) local root = mount(createElement(Test), container) return function() unmount(root) - roactAnimation.Parent = ReplicatedStorage + ReactAnimation.Parent = ReplicatedStorage end end diff --git a/wally.toml b/wally.toml index 768f184..02f8bd4 100644 --- a/wally.toml +++ b/wally.toml @@ -1,10 +1,10 @@ [package] -name = "nexure/roact-animation" +name = "nexure/react-animation" version = "0.1.1" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" [dependencies] -Roact = "core-packages/roact-compat@17.0.1-rc.16.1" +React = "core-packages/roact-compat@17.0.1-rc.16.1" Promise = "evaera/promise@4.0.0" From 9132c6e202bfc3a694283606198ac4ca061ac3e2 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Thu, 6 Jul 2023 14:33:35 -0400 Subject: [PATCH 03/22] Update entrypoint --- test/init.client.lua | 5 +++-- test/init.story.lua | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/test/init.client.lua b/test/init.client.lua index 59a2726..73352c2 100644 --- a/test/init.client.lua +++ b/test/init.client.lua @@ -8,12 +8,13 @@ ReplicatedStorage.ReactAnimation.Parent = Packages local Test = require(script.Test) local React = require(Packages.React) +local ReactRoblox = require(Packages.ReactRoblox) local createElement = React.createElement -local mount = React.mount if not LocalPlayer.Character then LocalPlayer.CharacterAdded:Wait() task.wait(4) end -mount(createElement("ScreenGui", {}, { Test = createElement(Test) }), LocalPlayer:WaitForChild("PlayerGui")) +local root = ReactRoblox.createRoot(LocalPlayer:WaitForChild("PlayerGui")) +root:render(createElement("ScreenGui", {}, { Test = createElement(Test) })) diff --git a/test/init.story.lua b/test/init.story.lua index 7c54d5a..bc1b30f 100644 --- a/test/init.story.lua +++ b/test/init.story.lua @@ -2,18 +2,19 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") local Packages = ReplicatedStorage.Packages local React = require(Packages.React) +local ReactRoblox = require(Packages.ReactRoblox) local createElement = React.createElement -local mount, unmount = React.mount, React.unmount return function(container) local ReactAnimation = ReplicatedStorage.ReactAnimation ReactAnimation.Parent = Packages local Test = require(script.Parent.Test) - local root = mount(createElement(Test), container) + local root = ReactRoblox.createRoot(container) + root:render(createElement(Test)) return function() - unmount(root) ReactAnimation.Parent = ReplicatedStorage + root:unmount() end end From 1ed3454c8d4d1423aa9e38156d84debd24b8cf22 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Thu, 6 Jul 2023 14:33:43 -0400 Subject: [PATCH 04/22] Update to use regular react --- wally.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wally.toml b/wally.toml index 02f8bd4..d549419 100644 --- a/wally.toml +++ b/wally.toml @@ -5,6 +5,8 @@ registry = "https://github.com/UpliftGames/wally-index" realm = "shared" [dependencies] -React = "core-packages/roact-compat@17.0.1-rc.16.1" + +React = "core-packages/react@17.0.1-rc.18" +ReactRoblox = "core-packages/react-roblox@17.0.1-rc.16" Promise = "evaera/promise@4.0.0" From 1cb08882caeb01d6c9bd5585f43611a3af841c08 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Thu, 6 Jul 2023 14:34:50 -0400 Subject: [PATCH 05/22] Update to use bindings --- src/Hooks/useGroupAnimation.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Hooks/useGroupAnimation.lua b/src/Hooks/useGroupAnimation.lua index 2db5e64..d8f996e 100644 --- a/src/Hooks/useGroupAnimation.lua +++ b/src/Hooks/useGroupAnimation.lua @@ -1,5 +1,5 @@ local React = require(script.Parent.Parent.React) -local useState = React.useState +local useBinding = React.useBinding local useRef = React.useRef local GroupAnimationController = {} @@ -71,10 +71,10 @@ local function getStateContainer(defaults: DefaultProperties) local values = {} for name, value in defaults do - local state, setState = useState(value) + local binding, updateBinding = useBinding(value) - setters[name] = setState - values[name] = state + setters[name] = updateBinding + values[name] = binding end return setters, values From dc47a74779224e704a15e25a0eff002d5f99be40 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Thu, 6 Jul 2023 15:20:03 -0400 Subject: [PATCH 06/22] Add useSpring & useTween --- src/Hooks/useSpring.lua | 41 ++++++++++++++++++++++++++++++++++++++ src/Hooks/useTween.lua | 44 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/Hooks/useSpring.lua create mode 100644 src/Hooks/useTween.lua diff --git a/src/Hooks/useSpring.lua b/src/Hooks/useSpring.lua new file mode 100644 index 0000000..a72ab06 --- /dev/null +++ b/src/Hooks/useSpring.lua @@ -0,0 +1,41 @@ +local React = require(script.Parent.Parent.React) +local useRef = React.useRef +local createBinding = React.createBinding + +local Spring = require(script.Parent.Parent.Animations.Types.Spring) + +local function makeSpring(props: Spring.SpringProperties, updater: (any) -> nil) + local newSpring = Spring.new(props) + newSpring:SetListener(updater) + + return newSpring +end + +local function useSpring(props: Spring.SpringProperties) + local controller = useRef() + local spring = controller.current + + local binding, update = createBinding(props.start) + + if not spring then + local newController = makeSpring(props, update) + spring = { + start = function(subProps: Spring.SpringProperties) + newController.props.target = subProps.target or newController.props.target + newController.props.speed = subProps.speed or newController.props.speed + newController.props.damper = subProps.damper or newController.props.damper + newController:Play(subProps.start or binding:getValue(), subProps.force) + end, + + stop = function() + newController:Stop() + end, + } + + controller.current = spring + end + + return binding, spring.start, spring.stop +end + +return useSpring diff --git a/src/Hooks/useTween.lua b/src/Hooks/useTween.lua new file mode 100644 index 0000000..dedf502 --- /dev/null +++ b/src/Hooks/useTween.lua @@ -0,0 +1,44 @@ +local React = require(script.Parent.Parent.React) +local useRef = React.useRef +local createBinding = React.createBinding + +local Tween = require(script.Parent.Parent.Animations.Types.Tween) + +local function makeTween(props: Tween.TweenProperties, updater: (any) -> nil) + local newSpring = Tween.new(props) + newSpring:SetListener(updater) + + return newSpring +end + +local function useTween(props: Tween.TweenProperties) + local controller = useRef() + local tween = controller.current + + local binding, update = createBinding(props.start) + + if not tween then + local newController = makeTween(props, update) + + tween = { + start = function(subProps: Tween.TweenProperties) + newController.props.info = subProps.info or newController.props.info + newController.props.start = subProps.start or newController.props.start + newController.props.target = subProps.target or newController.props.target + newController.props.immediate = subProps.immediate or newController.props.immediate + newController.props.delay = subProps.delay or newController.props.delay + newController:Play(subProps.start or binding:getValue()) + end, + + stop = function() + newController:Stop() + end, + } + + controller.current = tween + end + + return binding, tween.start, tween.stop +end + +return useTween From 47891268a14f5e5ae4c04a6704df692fd86a41e0 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Thu, 6 Jul 2023 15:20:13 -0400 Subject: [PATCH 07/22] Fix types, add impulse --- src/Animations/Types/Spring.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Animations/Types/Spring.lua b/src/Animations/Types/Spring.lua index aa0d828..6b16676 100644 --- a/src/Animations/Types/Spring.lua +++ b/src/Animations/Types/Spring.lua @@ -11,8 +11,8 @@ export type Spring = typeof(Spring.new()) export type SpringProperties = { damper: number?, speed: number?, - from: any, - to: any, + start: any, + target: any, } local Springs = {} :: { @@ -31,7 +31,7 @@ function Spring.new(props: SpringProperties) return self end -function Spring:Play(from: any?) +function Spring:Play(from: any?, force: Vector3?) if self.playing then self:Stop() end @@ -50,6 +50,10 @@ function Spring:Play(from: any?) local newSpring = SpringValue.new(baseFromValue, self.props.speed, self.props.damper) newSpring:SetGoal(baseToValue) + if force then + newSpring:Impulse(force) + end + Springs[newSpring] = { resolve = resolve, update = function() From 799ea68e914fd46dfc78bbd81b49f2937abc414d Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Thu, 6 Jul 2023 15:20:33 -0400 Subject: [PATCH 08/22] Add hook for listening to bindings --- src/Hooks/useBindings.lua | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/Hooks/useBindings.lua diff --git a/src/Hooks/useBindings.lua b/src/Hooks/useBindings.lua new file mode 100644 index 0000000..e1b2950 --- /dev/null +++ b/src/Hooks/useBindings.lua @@ -0,0 +1,45 @@ +local React = require(script.Parent.Parent.React) +local useEffect = React.useEffect +-- TODO: Remove this when we have a better way to subscribe to bindings +local subscribeToBinding = React.__subscribeToBinding + +local function useBindings(callback: (any...) -> (), bindings: { any }, deps: { table }) + useEffect(function() + local disconnects = {} + local values = {} + + local running = true + + for i, binding in bindings do + values[i] = binding:getValue() + end + + for i, binding in bindings do + local idx = i + + disconnects[idx] = subscribeToBinding(binding, function(newValue) + if not running then + warn("Binding updated after unmount") + return + end + + values[idx] = newValue + callback(unpack(values)) + end) + end + + callback(unpack(values)) + + return function() + for _, disconnect in disconnects do + disconnect() + end + + running = false + table.clear(disconnects) + table.clear(values) + end + end, { unpack(bindings), unpack(deps or {}) }) +end + +return useBindings From ede10d755c218638f7113679119f2ff1a99f4ed6 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Thu, 6 Jul 2023 15:20:42 -0400 Subject: [PATCH 09/22] Fix tween types --- src/Animations/Types/Tween.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Animations/Types/Tween.lua b/src/Animations/Types/Tween.lua index 01c22d0..ea05b45 100644 --- a/src/Animations/Types/Tween.lua +++ b/src/Animations/Types/Tween.lua @@ -8,9 +8,12 @@ local Tween = {} Tween.__index = Tween export type Tween = typeof(Tween.new()) -export type TweenProperties = { - from: any, - to: any, +export type TweenProperties = { + info: TweenInfo, + start: T, + target: T, + immediate: boolean?, + delay: number?, } local function playTween(tweenInfo, callback: (number) -> nil, completed: () -> nil) @@ -37,7 +40,7 @@ local function playTween(tweenInfo, callback: (number) -> nil, completed: () -> end end -function Tween.new(props: TweenProperties) +function Tween.new(props: TweenProperties) local self = setmetatable(BaseAnimation.new(), Tween) :: Tween self.props = props From 0666adf8df1104bc4c21e09190f783a980bd94f4 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Thu, 6 Jul 2023 15:20:47 -0400 Subject: [PATCH 10/22] Export new hooks --- src/init.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/init.lua b/src/init.lua index a06edcc..c6f9575 100644 --- a/src/init.lua +++ b/src/init.lua @@ -7,4 +7,9 @@ return { useAnimation = require(script.Hooks.useAnimation), useGroupAnimation = require(script.Hooks.useGroupAnimation), useSequenceAnimation = require(script.Hooks.useSequenceAnimation), + + useSpring = require(script.Hooks.useSpring), + useTween = require(script.Hooks.useTween), + + useBindings = require(script.Hooks.useBindings), } From 89678f119fa613d4342fbe48be6ab333455e3694 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Thu, 6 Jul 2023 15:20:53 -0400 Subject: [PATCH 11/22] Update test script --- test/Test.lua | 124 ++++++++++++++++++++++++++++---------------------- 1 file changed, 70 insertions(+), 54 deletions(-) diff --git a/test/Test.lua b/test/Test.lua index 69a41e2..5bf5bb5 100644 --- a/test/Test.lua +++ b/test/Test.lua @@ -8,7 +8,10 @@ local createElement = React.createElement local ReactAnimation = require(Packages.ReactAnimation) local useGroupAnimation = ReactAnimation.useGroupAnimation local useSequenceAnimation = ReactAnimation.useSequenceAnimation -local useAnimation = ReactAnimation.useAnimation +local useSpring = ReactAnimation.useSpring +local useTween = ReactAnimation.useTween +local useBindings = ReactAnimation.useBindings +-- local useAnimation = ReactAnimation.useAnimation -- local Spring = ReactAnimation.Spring local Tween = ReactAnimation.Tween @@ -119,58 +122,71 @@ local function TestSequence() end -- -- @TestTween --- local function TestTween() --- local value, update = useTween(UDim2.fromScale(0, 0), TweenInfo.new(1)) --- local value2, update2 = useTween(Color3.new(), TweenInfo.new(1)) - --- if value.X.Scale == 0 then --- update({ goal = UDim2.fromScale(1, 0) }) --- elseif (value :: UDim2).X.Scale == 1 then --- update({ goal = UDim2.fromScale(0, 0) }) --- end - --- if value2.R == 0 then --- update2({ start = Color3.new(1, 0, 0), goal = Color3.new(1, 0, 0) }) --- elseif value2.R == 1 then --- update2({ goal = Color3.new(0, 0, 0) }) --- end - --- return createFrame({ --- Name = "Tween", --- Position = value, --- BackgroundColor3 = value2, --- }) --- end +local function TestTween() + local value, update = useTween({ + info = TweenInfo.new(1), + start = UDim2.fromScale(0, 0), + }) + + local value2, update2 = useTween({ + info = TweenInfo.new(1), + start = Color3.new(), + }) + + useBindings(function(v, v2) + if v.X.Scale == 0 then + update({ target = UDim2.fromScale(1, 0) }) + elseif (v :: UDim2).X.Scale == 1 then + update({ target = UDim2.fromScale(0, 0) }) + end + + if v2.R == 0 then + update2({ start = Color3.new(1, 0, 0), target = Color3.new(1, 0, 0) }) + elseif v2.R == 1 then + update2({ target = Color3.new(0, 0, 0) }) + end + end, { value, value2 }) + + return createFrame({ + Name = "Tween", + Position = value, + BackgroundColor3 = value2, + }) +end -- -- @TestSpring --- local function TestSpring() --- local value, update = useSpring(UDim2.fromScale(-1, 0), 10, 0.5) - --- useEffect(function() --- local running = true - --- task.spawn(function() --- while running do --- update({ goal = UDim2.fromScale(1, 0) }) --- task.wait(3) - --- if running then --- update({ goal = UDim2.fromScale(-1, 0) }) --- task.wait(3) --- end --- end --- end) - --- return function() --- running = false --- end --- end, {}) - --- return createFrame({ --- Name = "Spring", --- Position = value, --- }) --- end +local function TestSpring() + local value, update = useSpring({ + start = UDim2.fromScale(0, 0), + speed = 5, + damper = 0.7, + }) + + useEffect(function() + local running = true + + task.spawn(function() + while running do + update({ target = UDim2.fromScale(1, 0) }) + task.wait(3) + + if running then + update({ target = UDim2.fromScale(-1, 0) }) + task.wait(3) + end + end + end) + + return function() + running = false + end + end, {}) + + return createFrame({ + Name = "Spring", + Position = value, + }) +end -- @entry local function Test() @@ -183,9 +199,9 @@ local function Test() VerticalAlignment = Enum.VerticalAlignment.Center, }), - -- tween = createElement(TestTween), - -- spring = createElement(TestSpring), - sequence = createElement(TestSequence), + tween = createElement(TestTween), + spring = createElement(TestSpring), + -- sequence = createElement(TestSequence), }) end From 9b9acf6827d20de0fc2552a87024d388d1885c64 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Fri, 7 Jul 2023 13:38:43 -0400 Subject: [PATCH 12/22] Added promise cancel --- src/Utility/SpringValue.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Utility/SpringValue.lua b/src/Utility/SpringValue.lua index 402fd7f..c4ca39b 100644 --- a/src/Utility/SpringValue.lua +++ b/src/Utility/SpringValue.lua @@ -71,6 +71,7 @@ function SpringValue:Update(dt: number) end self._current = LinearValue.new(self._current._ccstr, unpack(newValues)) + return updated end @@ -83,7 +84,11 @@ function SpringValue:Stop() end function SpringValue:Run(update: () -> ()) - return Promise.new(function(resolve) + return Promise.new(function(resolve, _, onCancel) + onCancel(function() + self:Stop() + end) + update(self:GetValue()) SpringValues[self] = { update, resolve } end) From d5c63f43c27d12dbc5eec487a71627dcbfc90c15 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Fri, 7 Jul 2023 13:38:58 -0400 Subject: [PATCH 13/22] Use SpringValue as controller --- src/Animations/Types/Spring.lua | 49 ++++++--------------------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/src/Animations/Types/Spring.lua b/src/Animations/Types/Spring.lua index 6b16676..fa425b0 100644 --- a/src/Animations/Types/Spring.lua +++ b/src/Animations/Types/Spring.lua @@ -1,5 +1,3 @@ -local RunService = game:GetService("RunService") - local BaseAnimation = require(script.Parent.Parent.Base) local Promise = require(script.Parent.Parent.Parent.Promise) local SpringValue = require(script.Parent.Parent.Parent.Utility.SpringValue) @@ -15,13 +13,6 @@ export type SpringProperties = { target: any, } -local Springs = {} :: { - [Spring]: { - completed: () -> nil, - resolve: () -> (), - }, -} - function Spring.new(props: SpringProperties) local self = setmetatable(BaseAnimation.new(), Spring) :: Spring @@ -46,27 +37,17 @@ function Spring:Play(from: any?, force: Vector3?) return Promise.resolve() end - local animation = Promise.new(function(resolve, _, onCancel) - local newSpring = SpringValue.new(baseFromValue, self.props.speed, self.props.damper) + local newSpring = SpringValue.new(baseFromValue, self.props.speed, self.props.damper) + newSpring:SetGoal(baseToValue) - newSpring:SetGoal(baseToValue) - if force then - newSpring:Impulse(force) - end + if force then + newSpring:Impulse(force) + end - Springs[newSpring] = { - resolve = resolve, - update = function() - if self.listener then - self.listener(newSpring:GetValue()) - end - end, - } - - onCancel(function() - Springs[newSpring] = nil - resolve() - end) + local animation = newSpring:Run(function() + if self.listener then + self.listener(newSpring:GetValue()) + end end) self.playing = true @@ -88,16 +69,4 @@ function Spring:Stop() self.playing = false end -RunService:BindToRenderStep("UPDATE_REACT_SPRINGS", Enum.RenderPriority.First.Value, function(dt: number) - for spring, data in Springs do - local didUpdate = spring:Update(dt) - data.update() - - if not didUpdate then - Springs[spring] = nil - data.resolve() - end - end -end) - return Spring From cbcb5292898001230f3104bc0c2d4e6064701da7 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Fri, 7 Jul 2023 13:50:54 -0400 Subject: [PATCH 14/22] Fix issue with listener --- src/Hooks/useSpring.lua | 14 ++++++-------- src/Hooks/useTween.lua | 13 +++++-------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/Hooks/useSpring.lua b/src/Hooks/useSpring.lua index a72ab06..7adbc6e 100644 --- a/src/Hooks/useSpring.lua +++ b/src/Hooks/useSpring.lua @@ -4,13 +4,6 @@ local createBinding = React.createBinding local Spring = require(script.Parent.Parent.Animations.Types.Spring) -local function makeSpring(props: Spring.SpringProperties, updater: (any) -> nil) - local newSpring = Spring.new(props) - newSpring:SetListener(updater) - - return newSpring -end - local function useSpring(props: Spring.SpringProperties) local controller = useRef() local spring = controller.current @@ -18,8 +11,11 @@ local function useSpring(props: Spring.SpringProperties) local binding, update = createBinding(props.start) if not spring then - local newController = makeSpring(props, update) + local newController = Spring.new(props) + spring = { + controller = newController, + start = function(subProps: Spring.SpringProperties) newController.props.target = subProps.target or newController.props.target newController.props.speed = subProps.speed or newController.props.speed @@ -35,6 +31,8 @@ local function useSpring(props: Spring.SpringProperties) controller.current = spring end + spring.controller:SetListener(update) + return binding, spring.start, spring.stop end diff --git a/src/Hooks/useTween.lua b/src/Hooks/useTween.lua index dedf502..882857e 100644 --- a/src/Hooks/useTween.lua +++ b/src/Hooks/useTween.lua @@ -4,13 +4,6 @@ local createBinding = React.createBinding local Tween = require(script.Parent.Parent.Animations.Types.Tween) -local function makeTween(props: Tween.TweenProperties, updater: (any) -> nil) - local newSpring = Tween.new(props) - newSpring:SetListener(updater) - - return newSpring -end - local function useTween(props: Tween.TweenProperties) local controller = useRef() local tween = controller.current @@ -18,9 +11,11 @@ local function useTween(props: Tween.TweenProperties) local binding, update = createBinding(props.start) if not tween then - local newController = makeTween(props, update) + local newController = Tween.new(props) tween = { + controller = newController, + start = function(subProps: Tween.TweenProperties) newController.props.info = subProps.info or newController.props.info newController.props.start = subProps.start or newController.props.start @@ -38,6 +33,8 @@ local function useTween(props: Tween.TweenProperties) controller.current = tween end + tween.controller:SetListener(update) + return binding, tween.start, tween.stop end From e6b1ec21af0980e49c8da99fc5c68062c1a0dce6 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Fri, 7 Jul 2023 14:19:52 -0400 Subject: [PATCH 15/22] Update useSpring to use base controller --- src/Hooks/useSpring.lua | 38 +++++++++++++++++++++++++++------- src/Utility/SpringValue.lua | 41 +++++++++++++++++++++++++++++++------ 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/Hooks/useSpring.lua b/src/Hooks/useSpring.lua index 7adbc6e..88f7895 100644 --- a/src/Hooks/useSpring.lua +++ b/src/Hooks/useSpring.lua @@ -2,6 +2,7 @@ local React = require(script.Parent.Parent.React) local useRef = React.useRef local createBinding = React.createBinding +local SpringValue = require(script.Parent.Parent.Utility.SpringValue) local Spring = require(script.Parent.Parent.Animations.Types.Spring) local function useSpring(props: Spring.SpringProperties) @@ -11,27 +12,50 @@ local function useSpring(props: Spring.SpringProperties) local binding, update = createBinding(props.start) if not spring then - local newController = Spring.new(props) + local newController = SpringValue.new(props.start, props.speed, props.damper) spring = { controller = newController, start = function(subProps: Spring.SpringProperties) - newController.props.target = subProps.target or newController.props.target - newController.props.speed = subProps.speed or newController.props.speed - newController.props.damper = subProps.damper or newController.props.damper - newController:Play(subProps.start or binding:getValue(), subProps.force) + if subProps.target then + newController:SetGoal(subProps.target) + end + + if subProps.start then + newController:SetValue(subProps.start) + end + + if subProps.force then + newController:Impulse(subProps.force) + end + + if subProps.damper then + newController:SetDamper(subProps.damper) + end + + if subProps.speed then + newController:SetSpeed(subProps.speed) + end + + if subProps.target or subProps.start or subProps.force then + if not newController:Playing() then + newController:Run() + end + end end, stop = function() - newController:Stop() + if newController:Playing() then + newController:Stop() + end end, } controller.current = spring end - spring.controller:SetListener(update) + spring.controller:SetUpdater(update) return binding, spring.start, spring.stop end diff --git a/src/Utility/SpringValue.lua b/src/Utility/SpringValue.lua index c4ca39b..a12ef46 100644 --- a/src/Utility/SpringValue.lua +++ b/src/Utility/SpringValue.lua @@ -18,6 +18,7 @@ function SpringValue.new(initial: LinearValue.LinearValueType, speed: number?, d _velocities = {}, _speed = speed or 1, _damper = damper or 1, + _updater = nil, }, SpringValue) end @@ -37,6 +38,22 @@ function SpringValue:SetGoal(goal: LinearValue.LinearValueType) self._goal = LinearValue.fromValue(goal) end +function SpringValue:SetSpeed(speed: number) + self._speed = speed +end + +function SpringValue:SetDamper(damper: number) + self._damper = damper +end + +function SpringValue:SetUpdater(updater: (any) -> ()) + self._updater = updater + + if self:Playing() and updater then + updater(self:GetValue()) + end +end + function SpringValue:GetGoal() return self._goal:ToValue() end @@ -75,6 +92,10 @@ function SpringValue:Update(dt: number) return updated end +function SpringValue:Playing() + return SpringValues[self] ~= nil +end + function SpringValue:Stop() local value = SpringValues[self] if value then @@ -83,14 +104,21 @@ function SpringValue:Stop() end end -function SpringValue:Run(update: () -> ()) +function SpringValue:Run(update: () -> ()?) + if update then + self._updater = update + end + return Promise.new(function(resolve, _, onCancel) onCancel(function() self:Stop() end) - update(self:GetValue()) - SpringValues[self] = { update, resolve } + if update then + update(self:GetValue()) + end + + SpringValues[self] = resolve end) end @@ -135,12 +163,13 @@ end RunService:UnbindFromRenderStep("UPDATE_SPRING_VALUES") RunService:BindToRenderStep("UPDATE_SPRING_VALUES", Enum.RenderPriority.First.Value, function(dt: number) - for spring, updates in pairs(SpringValues) do + for spring, resolve in pairs(SpringValues) do local didUpdate = spring:Update(dt) local value = spring:GetValue() - local update, resolve = unpack(updates) - update(value) + if spring._updater then + spring._updater(value) + end if not didUpdate then SpringValues[spring] = nil From 0a586726860c21fe7bb21d39c1db1cc789823426 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Tue, 11 Jul 2023 00:15:21 -0400 Subject: [PATCH 16/22] Updated createBinding -> useBinding --- src/Hooks/useSpring.lua | 4 ++-- src/Hooks/useTween.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Hooks/useSpring.lua b/src/Hooks/useSpring.lua index 88f7895..b69a2d3 100644 --- a/src/Hooks/useSpring.lua +++ b/src/Hooks/useSpring.lua @@ -1,6 +1,6 @@ local React = require(script.Parent.Parent.React) local useRef = React.useRef -local createBinding = React.createBinding +local useBinding = React.useBinding local SpringValue = require(script.Parent.Parent.Utility.SpringValue) local Spring = require(script.Parent.Parent.Animations.Types.Spring) @@ -9,7 +9,7 @@ local function useSpring(props: Spring.SpringProperties) local controller = useRef() local spring = controller.current - local binding, update = createBinding(props.start) + local binding, update = useBinding(props.start) if not spring then local newController = SpringValue.new(props.start, props.speed, props.damper) diff --git a/src/Hooks/useTween.lua b/src/Hooks/useTween.lua index 882857e..6cc791f 100644 --- a/src/Hooks/useTween.lua +++ b/src/Hooks/useTween.lua @@ -1,6 +1,6 @@ local React = require(script.Parent.Parent.React) local useRef = React.useRef -local createBinding = React.createBinding +local useBinding = React.useBinding local Tween = require(script.Parent.Parent.Animations.Types.Tween) @@ -8,7 +8,7 @@ local function useTween(props: Tween.TweenProperties) local controller = useRef() local tween = controller.current - local binding, update = createBinding(props.start) + local binding, update = useBinding(props.start) if not tween then local newController = Tween.new(props) From 86c28426cba63a517793b23a00971b0e30cd7fc4 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Tue, 11 Jul 2023 01:38:55 -0400 Subject: [PATCH 17/22] Fix tween --- src/Animations/Types/Tween.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Animations/Types/Tween.lua b/src/Animations/Types/Tween.lua index ea05b45..ae43043 100644 --- a/src/Animations/Types/Tween.lua +++ b/src/Animations/Types/Tween.lua @@ -55,7 +55,7 @@ function Tween:Play(from: any?) end local tweenInfo = self.props.info :: TweenInfo - local baseFromValue = from or self.props.start :: any + local baseFromValue = self.props.start or from :: any local baseToValue = self.props.target :: any -- start immediately will start the tween but not update the listener @@ -78,7 +78,7 @@ function Tween:Play(from: any?) assert(tweenInfo.Reverses == false, "Reverses must be false") assert(tweenInfo.DelayTime == 0, "DelayTime must be 0") - if from == baseToValue then + if baseFromValue == baseToValue then return Promise.resolve() end From 901887648919786013a3628e1f6913c48d8bb859 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Tue, 11 Jul 2023 21:28:29 -0400 Subject: [PATCH 18/22] Spring fixes, fix terms to be more accurate --- src/Animations/Types/Spring.lua | 16 +++++++++++----- src/Animations/Types/Tween.lua | 4 ++-- src/Utility/SpringValue.lua | 6 +++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Animations/Types/Spring.lua b/src/Animations/Types/Spring.lua index fa425b0..288c475 100644 --- a/src/Animations/Types/Spring.lua +++ b/src/Animations/Types/Spring.lua @@ -22,24 +22,29 @@ function Spring.new(props: SpringProperties) return self end -function Spring:Play(from: any?, force: Vector3?) +function Spring:Play(from: any?) if self.playing then self:Stop() end - local baseFromValue = from or self.props.start :: any + local baseFromValue = self.props.start or from :: any local baseToValue = self.props.target :: any + local force = self.props.force :: any - assert(baseFromValue, "No from value provided") - assert(baseToValue, "No to value provided") + assert(baseFromValue, "No start value provided") + assert(baseToValue, "No target value provided") - if from == baseToValue then + if baseFromValue == baseToValue and not force then return Promise.resolve() end local newSpring = SpringValue.new(baseFromValue, self.props.speed, self.props.damper) newSpring:SetGoal(baseToValue) + if self._oldSpring then + newSpring:Impulse(self._oldSpring:GetVelocity()) + end + if force then newSpring:Impulse(force) end @@ -52,6 +57,7 @@ function Spring:Play(from: any?, force: Vector3?) self.playing = true self.player = animation + self._oldSpring = newSpring return animation end diff --git a/src/Animations/Types/Tween.lua b/src/Animations/Types/Tween.lua index ae43043..b53a0e2 100644 --- a/src/Animations/Types/Tween.lua +++ b/src/Animations/Types/Tween.lua @@ -70,8 +70,8 @@ function Tween:Play(from: any?) end end - assert(baseFromValue, "No from value provided") - assert(baseToValue, "No to value provided") + assert(baseFromValue, "No start value provided") + assert(baseToValue, "No target value provided") assert(tweenInfo, "No tween info provided") assert(tweenInfo.RepeatCount == 0, "RepeatCount must be 0") diff --git a/src/Utility/SpringValue.lua b/src/Utility/SpringValue.lua index a12ef46..df78aef 100644 --- a/src/Utility/SpringValue.lua +++ b/src/Utility/SpringValue.lua @@ -34,6 +34,10 @@ function SpringValue:Impulse(impulse: LinearValue.LinearValueType) end end +function SpringValue:GetVelocity() + return LinearValue.new(self._current._ccstr, unpack(self._velocities)):ToValue() +end + function SpringValue:SetGoal(goal: LinearValue.LinearValueType) self._goal = LinearValue.fromValue(goal) end @@ -100,7 +104,7 @@ function SpringValue:Stop() local value = SpringValues[self] if value then SpringValues[self] = nil - value[2]() + value() end end From 2571901f8bda772d5772ce5c642382723aad7073 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Tue, 11 Jul 2023 21:50:12 -0400 Subject: [PATCH 19/22] Fix sequence overriding --- src/Hooks/useSequenceAnimation.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Hooks/useSequenceAnimation.lua b/src/Hooks/useSequenceAnimation.lua index 532fd2f..be257e4 100644 --- a/src/Hooks/useSequenceAnimation.lua +++ b/src/Hooks/useSequenceAnimation.lua @@ -56,6 +56,7 @@ function Sequence:Play(fromProps: SequenceProps) local animation = Promise.new(function(resolve, _, onCancel) local promises = {} + local playing = {} for _, sequenced in self.animation do local animationPromise = Promise.delay(sequenced.timestamp):andThen(function() @@ -66,7 +67,14 @@ function Sequence:Play(fromProps: SequenceProps) continue end - table.insert(allAnimatables, animatable:Play(fromProps[name])) + if playing[name] then + playing[name]:cancel() + end + + local promise = animatable:Play(fromProps[name]) + playing[name] = promise + + table.insert(allAnimatables, promise) end return Promise.all(allAnimatables) From 858a69baafd488648b5f9c601b6bc0446ce1b79f Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Tue, 11 Jul 2023 22:36:54 -0400 Subject: [PATCH 20/22] Update tween immediate --- src/Animations/Types/Tween.lua | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Animations/Types/Tween.lua b/src/Animations/Types/Tween.lua index b53a0e2..251a5e1 100644 --- a/src/Animations/Types/Tween.lua +++ b/src/Animations/Types/Tween.lua @@ -10,9 +10,9 @@ Tween.__index = Tween export type Tween = typeof(Tween.new()) export type TweenProperties = { info: TweenInfo, + startImmediate: T?, start: T, target: T, - immediate: boolean?, delay: number?, } @@ -33,6 +33,7 @@ local function playTween(tweenInfo, callback: (number) -> nil, completed: () -> end) return function() + callback(0) tween:Play() end, function() numberValue:Destroy() @@ -55,18 +56,18 @@ function Tween:Play(from: any?) end local tweenInfo = self.props.info :: TweenInfo - local baseFromValue = self.props.start or from :: any + local baseFromValue = self.props.startImmediate or self.props.start or from :: any local baseToValue = self.props.target :: any -- start immediately will start the tween but not update the listener - local startImmediately = self.props.immediate == true + local startImmediately = self.props.startImmediate ~= nil local delayTime = self.props.delay :: number if not delayTime then assert(startImmediately == false, "Cannot start immediately without a delay") else if startImmediately then - assert(delayTime < tweenInfo.Time, "DelayTime must be less than Time") + assert(delayTime > 0, "DelayTime must be greater than zero") end end @@ -86,13 +87,7 @@ function Tween:Play(from: any?) local toValue = LinearValue.fromValue(baseToValue) local animation = Promise.new(function(resolve, _, onCancel) - local canUpdate = true - local play, cancel = playTween(tweenInfo, function(value) - if not canUpdate then - return - end - local newValue = fromValue:Lerp(toValue, value):ToValue() self.listener(newValue) end, function() @@ -107,15 +102,11 @@ function Tween:Play(from: any?) play() else if startImmediately then - canUpdate = false - play() - - task.wait(delayTime) - canUpdate = true - else - task.wait(delayTime) - play() + self.listener(baseFromValue) end + + task.wait(delayTime) + play() end end) From 0c1c9173e71c3cd2510e146b8d0e6bce45f9894d Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Tue, 25 Jul 2023 22:01:06 -0400 Subject: [PATCH 21/22] Added type checks for update --- src/Hooks/useGroupAnimation.lua | 2 ++ src/Hooks/useSpring.lua | 2 ++ src/Hooks/useTween.lua | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Hooks/useGroupAnimation.lua b/src/Hooks/useGroupAnimation.lua index d8f996e..49c169f 100644 --- a/src/Hooks/useGroupAnimation.lua +++ b/src/Hooks/useGroupAnimation.lua @@ -90,6 +90,8 @@ local function useGroupAnimation(props: GroupAnimation, default: DefaultProperti current = { play = function(newState: string) + assert(typeof(newState) == "string", "useGroupAnimation expects a string 'state'") + newController:Play(newState) end, diff --git a/src/Hooks/useSpring.lua b/src/Hooks/useSpring.lua index b69a2d3..20779f4 100644 --- a/src/Hooks/useSpring.lua +++ b/src/Hooks/useSpring.lua @@ -18,6 +18,8 @@ local function useSpring(props: Spring.SpringProperties) controller = newController, start = function(subProps: Spring.SpringProperties) + assert(typeof(subProps) == "table", "useSpring expects a table of properties") + if subProps.target then newController:SetGoal(subProps.target) end diff --git a/src/Hooks/useTween.lua b/src/Hooks/useTween.lua index 6cc791f..9f65a22 100644 --- a/src/Hooks/useTween.lua +++ b/src/Hooks/useTween.lua @@ -17,6 +17,8 @@ local function useTween(props: Tween.TweenProperties) controller = newController, start = function(subProps: Tween.TweenProperties) + assert(typeof(subProps) == "table", "useTween expects a table of properties") + newController.props.info = subProps.info or newController.props.info newController.props.start = subProps.start or newController.props.start newController.props.target = subProps.target or newController.props.target From 49fe90b5e60e575615b34227b6b8b931a3388b71 Mon Sep 17 00:00:00 2001 From: Jack Fox <0xdeadbeef1@gmail.com> Date: Tue, 25 Jul 2023 22:35:06 -0400 Subject: [PATCH 22/22] fix start value for tweens --- src/Hooks/useTween.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hooks/useTween.lua b/src/Hooks/useTween.lua index 9f65a22..17ee60d 100644 --- a/src/Hooks/useTween.lua +++ b/src/Hooks/useTween.lua @@ -20,7 +20,7 @@ local function useTween(props: Tween.TweenProperties) assert(typeof(subProps) == "table", "useTween expects a table of properties") newController.props.info = subProps.info or newController.props.info - newController.props.start = subProps.start or newController.props.start + newController.props.start = subProps.start or binding:getValue() newController.props.target = subProps.target or newController.props.target newController.props.immediate = subProps.immediate or newController.props.immediate newController.props.delay = subProps.delay or newController.props.delay