UNPKG

@shopify/react-native-skia

Version:

High-performance React Native Graphics using Skia

145 lines (135 loc) 3.8 kB
import type { ExtrapolationType, FrameInfo, SharedValue, } from "react-native-reanimated"; import { useCallback, useMemo } from "react"; import type { SkPath, SkPathBuilder, SkPoint } from "../../skia/types"; import { interpolatePaths, interpolateVector } from "../../animation"; import { Skia } from "../../skia"; import { isOnMainThread } from "../../renderer/Offscreen"; import Rea from "./ReanimatedProxy"; export const notifyChange = <T>(value: SharedValue<T>) => { "worklet"; if (isOnMainThread()) { // eslint-disable-next-line @typescript-eslint/no-explicit-any (value as any)._value = value.value; } }; /** * Hook for creating animated paths using PathBuilder. * The callback receives a mutable PathBuilder that can be used to construct the path. * The resulting immutable SkPath is stored in a shared value. * * @param cb - Callback that receives the PathBuilder to construct the path * @param init - Optional initial path to add to the builder * @param transform - Optional transform function applied to the built path */ export const usePathValue = ( cb: (builder: SkPathBuilder) => void, init?: SkPath, transform?: (path: SkPath) => SkPath ) => { const builderInit = useMemo(() => Skia.PathBuilder.Make(), []); const pathInit = useMemo(() => Skia.Path.Make(), []); const builder = Rea.useSharedValue(builderInit); const path = Rea.useSharedValue(pathInit); Rea.useDerivedValue(() => { builder.value.reset(); if (init !== undefined) { builder.value.addPath(init); } cb(builder.value); let result = builder.value.build(); if (transform !== undefined) { result = transform(result); } path.value = result; notifyChange(path); }); return path; }; export const useClock = () => { const clock = Rea.useSharedValue(0); const callback = useCallback( (info: FrameInfo) => { "worklet"; clock.value = info.timeSinceFirstFrame; }, [clock] ); Rea.useFrameCallback(callback); return clock; }; /** * @worklet */ type Interpolator<T> = ( value: number, input: number[], output: T[], options: ExtrapolationType, result: T ) => T; const useInterpolator = <T>( factory: () => T, value: SharedValue<number>, interpolator: Interpolator<T>, input: number[], output: T[], options?: ExtrapolationType ) => { // eslint-disable-next-line react-hooks/exhaustive-deps const init = useMemo(() => factory(), []); const result = Rea.useSharedValue(init); Rea.useAnimatedReaction( () => value.value, (val) => { result.value = interpolator(val, input, output, options, result.value); notifyChange(result); }, [input, output, options] ); return result; }; export const usePathInterpolation = ( value: SharedValue<number>, input: number[], outputRange: SkPath[], options?: ExtrapolationType ) => { // Check if all paths in outputRange are interpolable const allPathsInterpolable = outputRange .slice(1) .every((path) => outputRange[0].isInterpolatable(path)); if (!allPathsInterpolable) { // Handle the case where not all paths are interpolable // For example, throw an error or return early throw new Error( `Not all paths in the output range are interpolable. See: https://shopify.github.io/react-native-skia/docs/animations/hooks#usepathinterpolation` ); } return useInterpolator( () => Skia.Path.Make(), value, interpolatePaths, input, outputRange, options ); }; export const useVectorInterpolation = ( value: SharedValue<number>, input: number[], outputRange: SkPoint[], options?: ExtrapolationType ) => useInterpolator( () => Skia.Point(0, 0), value, interpolateVector, input, outputRange, options );