diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index db30e512a5656..32cd659acf93d 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -63661,4 +63661,33 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. ==================================================================================================== -Total license count: 867 +==================================================================================================== +LIBRARY: react-native +ORIGIN: ../../../third_party/react-native/LICENSE +TYPE: LicenseType.mit +FILE: ../../../third_party/react-native/Libraries/Animated/animations/SpringAnimation.js +---------------------------------------------------------------------------------------------------- +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +==================================================================================================== + +Total license count: 868 diff --git a/third_party/react-native/LICENSE.txt b/third_party/react-native/LICENSE.txt new file mode 100644 index 0000000000000..b93be90515ccd --- /dev/null +++ b/third_party/react-native/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/react-native/Libraries/Animated/animations/SpringAnimation.js b/third_party/react-native/Libraries/Animated/animations/SpringAnimation.js new file mode 100644 index 0000000000000..69101dab030e8 --- /dev/null +++ b/third_party/react-native/Libraries/Animated/animations/SpringAnimation.js @@ -0,0 +1,370 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +import type {PlatformConfig} from '../AnimatedPlatformConfig'; +import type AnimatedInterpolation from '../nodes/AnimatedInterpolation'; +import type AnimatedValue from '../nodes/AnimatedValue'; +import type AnimatedValueXY from '../nodes/AnimatedValueXY'; +import type {AnimationConfig, EndCallback} from './Animation'; + +import NativeAnimatedHelper from '../NativeAnimatedHelper'; +import AnimatedColor from '../nodes/AnimatedColor'; +import * as SpringConfig from '../SpringConfig'; +import Animation from './Animation'; +import invariant from 'invariant'; + +export type SpringAnimationConfig = { + ...AnimationConfig, + toValue: + | number + | AnimatedValue + | { + x: number, + y: number, + ... + } + | AnimatedValueXY + | { + r: number, + g: number, + b: number, + a: number, + ... + } + | AnimatedColor + | AnimatedInterpolation, + overshootClamping?: boolean, + restDisplacementThreshold?: number, + restSpeedThreshold?: number, + velocity?: + | number + | { + x: number, + y: number, + ... + }, + bounciness?: number, + speed?: number, + tension?: number, + friction?: number, + stiffness?: number, + damping?: number, + mass?: number, + delay?: number, +}; + +export type SpringAnimationConfigSingle = { + ...AnimationConfig, + toValue: number, + overshootClamping?: boolean, + restDisplacementThreshold?: number, + restSpeedThreshold?: number, + velocity?: number, + bounciness?: number, + speed?: number, + tension?: number, + friction?: number, + stiffness?: number, + damping?: number, + mass?: number, + delay?: number, +}; + +export default class SpringAnimation extends Animation { + _overshootClamping: boolean; + _restDisplacementThreshold: number; + _restSpeedThreshold: number; + _lastVelocity: number; + _startPosition: number; + _lastPosition: number; + _fromValue: number; + _toValue: number; + _stiffness: number; + _damping: number; + _mass: number; + _initialVelocity: number; + _delay: number; + _timeout: any; + _startTime: number; + _lastTime: number; + _frameTime: number; + _onUpdate: (value: number) => void; + _animationFrame: any; + _useNativeDriver: boolean; + _platformConfig: ?PlatformConfig; + + constructor(config: SpringAnimationConfigSingle) { + super(); + + this._overshootClamping = config.overshootClamping ?? false; + this._restDisplacementThreshold = config.restDisplacementThreshold ?? 0.001; + this._restSpeedThreshold = config.restSpeedThreshold ?? 0.001; + this._initialVelocity = config.velocity ?? 0; + this._lastVelocity = config.velocity ?? 0; + this._toValue = config.toValue; + this._delay = config.delay ?? 0; + this._useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config); + this._platformConfig = config.platformConfig; + this.__isInteraction = config.isInteraction ?? !this._useNativeDriver; + this.__iterations = config.iterations ?? 1; + + if ( + config.stiffness !== undefined || + config.damping !== undefined || + config.mass !== undefined + ) { + invariant( + config.bounciness === undefined && + config.speed === undefined && + config.tension === undefined && + config.friction === undefined, + 'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one', + ); + this._stiffness = config.stiffness ?? 100; + this._damping = config.damping ?? 10; + this._mass = config.mass ?? 1; + } else if (config.bounciness !== undefined || config.speed !== undefined) { + // Convert the origami bounciness/speed values to stiffness/damping + // We assume mass is 1. + invariant( + config.tension === undefined && + config.friction === undefined && + config.stiffness === undefined && + config.damping === undefined && + config.mass === undefined, + 'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one', + ); + const springConfig = SpringConfig.fromBouncinessAndSpeed( + config.bounciness ?? 8, + config.speed ?? 12, + ); + this._stiffness = springConfig.stiffness; + this._damping = springConfig.damping; + this._mass = 1; + } else { + // Convert the origami tension/friction values to stiffness/damping + // We assume mass is 1. + const springConfig = SpringConfig.fromOrigamiTensionAndFriction( + config.tension ?? 40, + config.friction ?? 7, + ); + this._stiffness = springConfig.stiffness; + this._damping = springConfig.damping; + this._mass = 1; + } + + invariant(this._stiffness > 0, 'Stiffness value must be greater than 0'); + invariant(this._damping > 0, 'Damping value must be greater than 0'); + invariant(this._mass > 0, 'Mass value must be greater than 0'); + } + + __getNativeAnimationConfig(): {| + damping: number, + initialVelocity: number, + iterations: number, + mass: number, + platformConfig: ?PlatformConfig, + overshootClamping: boolean, + restDisplacementThreshold: number, + restSpeedThreshold: number, + stiffness: number, + toValue: any, + type: $TEMPORARY$string<'spring'>, + |} { + return { + type: 'spring', + overshootClamping: this._overshootClamping, + restDisplacementThreshold: this._restDisplacementThreshold, + restSpeedThreshold: this._restSpeedThreshold, + stiffness: this._stiffness, + damping: this._damping, + mass: this._mass, + initialVelocity: this._initialVelocity ?? this._lastVelocity, + toValue: this._toValue, + iterations: this.__iterations, + platformConfig: this._platformConfig, + }; + } + + start( + fromValue: number, + onUpdate: (value: number) => void, + onEnd: ?EndCallback, + previousAnimation: ?Animation, + animatedValue: AnimatedValue, + ): void { + this.__active = true; + this._startPosition = fromValue; + this._lastPosition = this._startPosition; + + this._onUpdate = onUpdate; + this.__onEnd = onEnd; + this._lastTime = Date.now(); + this._frameTime = 0.0; + + if (previousAnimation instanceof SpringAnimation) { + const internalState = previousAnimation.getInternalState(); + this._lastPosition = internalState.lastPosition; + this._lastVelocity = internalState.lastVelocity; + // Set the initial velocity to the last velocity + this._initialVelocity = this._lastVelocity; + this._lastTime = internalState.lastTime; + } + + const start = () => { + if (this._useNativeDriver) { + this.__startNativeAnimation(animatedValue); + } else { + this.onUpdate(); + } + }; + + // If this._delay is more than 0, we start after the timeout. + if (this._delay) { + this._timeout = setTimeout(start, this._delay); + } else { + start(); + } + } + + getInternalState(): Object { + return { + lastPosition: this._lastPosition, + lastVelocity: this._lastVelocity, + lastTime: this._lastTime, + }; + } + + /** + * This spring model is based off of a damped harmonic oscillator + * (https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator). + * + * We use the closed form of the second order differential equation: + * + * x'' + (2ζ⍵_0)x' + ⍵^2x = 0 + * + * where + * ⍵_0 = √(k / m) (undamped angular frequency of the oscillator), + * ζ = c / 2√mk (damping ratio), + * c = damping constant + * k = stiffness + * m = mass + * + * The derivation of the closed form is described in detail here: + * http://planetmath.org/sites/default/files/texpdf/39745.pdf + * + * This algorithm happens to match the algorithm used by CASpringAnimation, + * a QuartzCore (iOS) API that creates spring animations. + */ + onUpdate(): void { + // If for some reason we lost a lot of frames (e.g. process large payload or + // stopped in the debugger), we only advance by 4 frames worth of + // computation and will continue on the next frame. It's better to have it + // running at faster speed than jumping to the end. + const MAX_STEPS = 64; + let now = Date.now(); + if (now > this._lastTime + MAX_STEPS) { + now = this._lastTime + MAX_STEPS; + } + + const deltaTime = (now - this._lastTime) / 1000; + this._frameTime += deltaTime; + + const c: number = this._damping; + const m: number = this._mass; + const k: number = this._stiffness; + const v0: number = -this._initialVelocity; + + const zeta = c / (2 * Math.sqrt(k * m)); // damping ratio + const omega0 = Math.sqrt(k / m); // undamped angular frequency of the oscillator (rad/ms) + const omega1 = omega0 * Math.sqrt(1.0 - zeta * zeta); // exponential decay + const x0 = this._toValue - this._startPosition; // calculate the oscillation from x0 = 1 to x = 0 + + let position = 0.0; + let velocity = 0.0; + const t = this._frameTime; + if (zeta < 1) { + // Under damped + const envelope = Math.exp(-zeta * omega0 * t); + position = + this._toValue - + envelope * + (((v0 + zeta * omega0 * x0) / omega1) * Math.sin(omega1 * t) + + x0 * Math.cos(omega1 * t)); + // This looks crazy -- it's actually just the derivative of the + // oscillation function + velocity = + zeta * + omega0 * + envelope * + ((Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0)) / omega1 + + x0 * Math.cos(omega1 * t)) - + envelope * + (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) - + omega1 * x0 * Math.sin(omega1 * t)); + } else { + // Critically damped + const envelope = Math.exp(-omega0 * t); + position = this._toValue - envelope * (x0 + (v0 + omega0 * x0) * t); + velocity = + envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0)); + } + + this._lastTime = now; + this._lastPosition = position; + this._lastVelocity = velocity; + + this._onUpdate(position); + if (!this.__active) { + // a listener might have stopped us in _onUpdate + return; + } + + // Conditions for stopping the spring animation + let isOvershooting = false; + if (this._overshootClamping && this._stiffness !== 0) { + if (this._startPosition < this._toValue) { + isOvershooting = position > this._toValue; + } else { + isOvershooting = position < this._toValue; + } + } + const isVelocity = Math.abs(velocity) <= this._restSpeedThreshold; + let isDisplacement = true; + if (this._stiffness !== 0) { + isDisplacement = + Math.abs(this._toValue - position) <= this._restDisplacementThreshold; + } + + if (isOvershooting || (isVelocity && isDisplacement)) { + if (this._stiffness !== 0) { + // Ensure that we end up with a round value + this._lastPosition = this._toValue; + this._lastVelocity = 0; + this._onUpdate(this._toValue); + } + + this.__debouncedOnEnd({finished: true}); + return; + } + // $FlowFixMe[method-unbinding] added when improving typing for this parameters + this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); + } + + stop(): void { + super.stop(); + this.__active = false; + clearTimeout(this._timeout); + global.cancelAnimationFrame(this._animationFrame); + this.__debouncedOnEnd({finished: false}); + } +}