diff --git a/native/components/full-screen-view-modal.react.js b/native/components/full-screen-view-modal.react.js --- a/native/components/full-screen-view-modal.react.js +++ b/native/components/full-screen-view-modal.react.js @@ -56,6 +56,7 @@ gestureJustStarted, gestureJustEnded, runTiming, + clampV2, } from '../utils/animation-utils.js'; const { @@ -90,34 +91,6 @@ decay, } = Animated; -function scaleDelta(value: Node, gestureActive: Node): Node { - const diffThisFrame = new Value(1); - const prevValue = new Value(1); - return cond( - gestureActive, - [ - set(diffThisFrame, divide(value, prevValue)), - set(prevValue, value), - diffThisFrame, - ], - set(prevValue, 1), - ); -} - -function panDelta(value: Node, gestureActive: Node): Node { - const diffThisFrame = new Value(0); - const prevValue = new Value(0); - return cond( - gestureActive, - [ - set(diffThisFrame, sub(value, prevValue)), - set(prevValue, value), - diffThisFrame, - ], - set(prevValue, 0), - ); -} - function runDecay( clock: Clock, velocity: Node, @@ -380,17 +353,6 @@ ); const updates = [ - this.doubleTapUpdate( - doubleTapState, - doubleTapX, - doubleTapY, - roundedCurScale, - zoomClock, - gestureActive, - curScale, - curX, - curY, - ), this.backdropOpacityUpdate( panJustEnded, pinchActive, @@ -492,79 +454,6 @@ return max(vertPop, 0); } - doubleTapUpdate( - // Inputs - doubleTapState: Node, - doubleTapX: Node, - doubleTapY: Node, - roundedCurScale: Node, - zoomClock: Clock, - gestureActive: Node, - // Outputs - curScale: Value, - curX: Value, - curY: Value, - ): Node { - const zoomClockRunning = clockRunning(zoomClock); - const zoomActive = and(not(gestureActive), zoomClockRunning); - const targetScale = cond(greaterThan(roundedCurScale, 1), 1, 3); - - const tapXDiff = sub(doubleTapX, this.centerX, curX); - const tapYDiff = sub(doubleTapY, this.centerY, curY); - const tapXPercent = divide(tapXDiff, this.imageWidth, curScale); - const tapYPercent = divide(tapYDiff, this.imageHeight, curScale); - - const horizPanSpace = this.horizontalPanSpace(targetScale); - const vertPanSpace = this.verticalPanSpace(targetScale); - const horizPanPercent = divide(horizPanSpace, this.imageWidth, targetScale); - const vertPanPercent = divide(vertPanSpace, this.imageHeight, targetScale); - - const tapXPercentClamped = clamp( - tapXPercent, - multiply(-1, horizPanPercent), - horizPanPercent, - ); - const tapYPercentClamped = clamp( - tapYPercent, - multiply(-1, vertPanPercent), - vertPanPercent, - ); - const targetX = multiply(tapXPercentClamped, this.imageWidth, targetScale); - const targetY = multiply(tapYPercentClamped, this.imageHeight, targetScale); - - const targetRelativeScale = divide(targetScale, curScale); - const targetRelativeX = multiply(-1, add(targetX, curX)); - const targetRelativeY = multiply(-1, add(targetY, curY)); - - const zoomScale = runTiming(zoomClock, 1, targetRelativeScale); - const zoomX = runTiming(zoomClock, 0, targetRelativeX, false); - const zoomY = runTiming(zoomClock, 0, targetRelativeY, false); - - const deltaScale = scaleDelta(zoomScale, zoomActive); - const deltaX = panDelta(zoomX, zoomActive); - const deltaY = panDelta(zoomY, zoomActive); - - const fingerJustReleased = and( - gestureJustEnded(doubleTapState), - // TODO: migrate this in the next diffs - // this.outsideButtons(doubleTapX, doubleTapY), - 1, - ); - - return cond( - [fingerJustReleased, deltaX, deltaY, deltaScale, gestureActive], - stopClock(zoomClock), - cond(or(zoomClockRunning, fingerJustReleased), [ - zoomX, - zoomY, - zoomScale, - set(curX, add(curX, deltaX)), - set(curY, add(curY, deltaY)), - set(curScale, multiply(curScale, deltaScale)), - ]), - ); - } - backdropOpacityUpdate( // Inputs panJustEnded: Node, @@ -1103,6 +992,32 @@ const centerX = useSharedValue(dimensions.width / 2); const centerY = useSharedValue(dimensions.safeAreaHeight / 2); + const frameWidth = useSharedValue(dimensions.width); + const frameHeight = useSharedValue(dimensions.safeAreaHeight); + const imageWidth = useSharedValue(props.contentDimensions.width); + const imageHeight = useSharedValue(props.contentDimensions.height); + + // How much space do we have to pan the image horizontally? + const getHorizontalPanSpace = React.useCallback( + (scale: number): number => { + 'worklet'; + const apparentWidth = imageWidth.value * scale; + const horizPop = (apparentWidth - frameWidth.value) / 2; + return Math.max(horizPop, 0); + }, + [frameWidth, imageWidth], + ); + + // How much space do we have to pan the image vertically? + const getVerticalPanSpace = React.useCallback( + (scale: number): number => { + 'worklet'; + const apparentHeight = imageHeight.value * scale; + const vertPop = (apparentHeight - frameHeight.value) / 2; + return Math.max(vertPop, 0); + }, + [frameHeight, imageHeight], + ); const lastPinchScale = useSharedValue(1); @@ -1245,6 +1160,57 @@ [outsideButtons, toggleActionLinks, toggleCloseButton, roundedCurScale], ); + const doubleTapUpdate = React.useCallback( + ({ x, y }: TapGestureEvent) => { + 'worklet'; + if (!outsideButtons(x, y)) { + return; + } + const targetScale = roundedCurScale.value > 1 ? 1 : 3; + + const tapXDiff = x - centerX.value - curX.value; + const tapYDiff = y - centerY.value - curY.value; + const tapXPercent = tapXDiff / imageWidth.value / curScale.value; + const tapYPercent = tapYDiff / imageHeight.value / curScale.value; + + const horizPanSpace = getHorizontalPanSpace(targetScale); + const vertPanSpace = getVerticalPanSpace(targetScale); + const horizPanPercent = horizPanSpace / imageWidth.value / targetScale; + const vertPanPercent = vertPanSpace / imageHeight.value / targetScale; + + const tapXPercentClamped = clampV2( + tapXPercent, + -horizPanPercent, + horizPanPercent, + ); + const tapYPercentClamped = clampV2( + tapYPercent, + -vertPanPercent, + vertPanPercent, + ); + + const targetX = tapXPercentClamped * imageWidth.value * targetScale; + const targetY = tapYPercentClamped * imageHeight.value * targetScale; + + curScale.value = withTiming(targetScale, defaultTimingConfig); + curX.value = withTiming(targetX, defaultTimingConfig); + curY.value = withTiming(targetY, defaultTimingConfig); + }, + [ + centerX, + centerY, + curScale, + curX, + curY, + getHorizontalPanSpace, + imageHeight, + imageWidth, + outsideButtons, + roundedCurScale, + getVerticalPanSpace, + ], + ); + const gesture = React.useMemo(() => { const pinchGesture = Gesture.Pinch() .onStart(pinchStart) @@ -1254,7 +1220,9 @@ .onStart(panStart) .onUpdate(panUpdate) .onEnd(panEnd); - const doubleTapGesture = Gesture.Tap().numberOfTaps(2); + const doubleTapGesture = Gesture.Tap() + .numberOfTaps(2) + .onEnd(doubleTapUpdate); const singleTapGesture = Gesture.Tap() .numberOfTaps(1) .onEnd(singleTapUpdate); @@ -1264,7 +1232,15 @@ doubleTapGesture, singleTapGesture, ); - }, [panEnd, panStart, panUpdate, pinchStart, pinchUpdate, singleTapUpdate]); + }, [ + doubleTapUpdate, + panEnd, + panStart, + panUpdate, + pinchStart, + pinchUpdate, + singleTapUpdate, + ]); return (