From 9562e4309f84529ac852b3971c6672e2ee27550e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arno=C5=A1t=20Pleskot?= Date: Mon, 10 Jul 2023 18:10:22 +0200 Subject: [PATCH] feat: add zoom lock and viewportZoomFactor --- src/components/App.tsx | 60 +++++++++++++++++++++++++++--------------- src/types.ts | 4 +++ 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index be56e086de..4deba45a9c 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -223,6 +223,7 @@ import { FrameNameBoundsCache, SidebarName, SidebarTabName, + NormalizedZoomValue, } from "../types"; import { debounce, @@ -7650,20 +7651,17 @@ class App extends React.Component { scrollConstraints, width, height, - zoom, + zoom: currentZoom, cursorButton, } = this.state; - // Set the overscroll allowance percentage - const OVERSCROLL_ALLOWANCE_PERCENTAGE = 0.2; - if (!scrollConstraints || scrollConstraints.isAnimating) { return null; } // Check if the state has changed since the last render const stateUnchanged = - zoom.value === prevState.zoom.value && + currentZoom.value === prevState.zoom.value && scrollX === prevState.scrollX && scrollY === prevState.scrollY && width === prevState.width && @@ -7671,10 +7669,17 @@ class App extends React.Component { cursorButton === prevState.cursorButton; // If the state hasn't changed and scrollConstraints didn't just get defined, return null - if (!scrollConstraints || (stateUnchanged && prevState.scrollConstraints)) { + if (stateUnchanged && prevState.scrollConstraints) { return null; } + // Set the overscroll allowance percentage + const OVERSCROLL_ALLOWANCE_PERCENTAGE = 0.2; + const lockZoom = scrollConstraints.opts?.lockZoom ?? false; + const viewportZoomFactor = scrollConstraints.opts?.viewportZoomFactor + ? Math.min(1, Math.max(scrollConstraints.opts.viewportZoomFactor, 0.1)) + : 0.9; + /** * Calculate the zoom levels on which will constrained area fits the viewport for each axis * @returns The zoom levels for the X and Y axes. @@ -7684,20 +7689,21 @@ class App extends React.Component { const scrollableHeight = scrollConstraints.height; const zoomLevelX = width / scrollableWidth; const zoomLevelY = height / scrollableHeight; - return { zoomLevelX, zoomLevelY }; + const maxZoomLevel = lockZoom + ? Math.min(zoomLevelX, zoomLevelY) * viewportZoomFactor + : null; + return { zoomLevelX, zoomLevelY, maxZoomLevel }; }; /** * Calculates the center position of the constrained scroll area. * @returns The X and Y coordinates of the center position. */ - const calculateConstrainedScrollCenter = () => { + const calculateConstrainedScrollCenter = (zoom: number) => { const constrainedScrollCenterX = - scrollConstraints.x + - (scrollConstraints.width - width / zoom.value) / -2; + scrollConstraints.x + (scrollConstraints.width - width / zoom) / -2; const constrainedScrollCenterY = - scrollConstraints.y + - (scrollConstraints.height - height / zoom.value) / -2; + scrollConstraints.y + (scrollConstraints.height - height / zoom) / -2; return { constrainedScrollCenterX, constrainedScrollCenterY }; }; @@ -7728,6 +7734,7 @@ class App extends React.Component { overscrollAllowanceY: number, constrainedScrollCenterX: number, constrainedScrollCenterY: number, + zoom: number, ) => { let maxScrollX; let minScrollX; @@ -7744,13 +7751,13 @@ class App extends React.Component { minScrollX = scrollConstraints.x - scrollConstraints.width + - width / zoom.value - + width / zoom - overscrollAllowanceX; maxScrollY = scrollConstraints.y + overscrollAllowanceY; minScrollY = scrollConstraints.y - scrollConstraints.height + - height / zoom.value - + height / zoom - overscrollAllowanceY; } else if (cursorButton !== "down" && shouldAdjustForCenteredView) { maxScrollX = constrainedScrollCenterX; @@ -7760,10 +7767,10 @@ class App extends React.Component { } else { maxScrollX = scrollConstraints.x; minScrollX = - scrollConstraints.x - scrollConstraints.width + width / zoom.value; + scrollConstraints.x - scrollConstraints.width + width / zoom; maxScrollY = scrollConstraints.y; minScrollY = - scrollConstraints.y - scrollConstraints.height + height / zoom.value; + scrollConstraints.y - scrollConstraints.height + height / zoom; } return { maxScrollX, minScrollX, maxScrollY, minScrollY }; @@ -7816,7 +7823,7 @@ class App extends React.Component { scrollY + height > scrollConstraints.y + scrollConstraints.height) && cursorButton !== "down" && - zoom.value === prevState.zoom.value + currentZoom.value === prevState.zoom.value ) { this.setState({ scrollConstraints: { ...scrollConstraints, isAnimating: true }, @@ -7849,7 +7856,14 @@ class App extends React.Component { const constrainedState = { scrollX: constrainedScrollX, scrollY: constrainedScrollY, - zoom, + zoom: { + value: maxZoomLevel + ? (Math.max( + currentZoom.value, + maxZoomLevel, + ) as NormalizedZoomValue) + : currentZoom.value, + }, }; this.setState(constrainedState); @@ -7860,13 +7874,16 @@ class App extends React.Component { }; // Compute the constrained scroll values. - const { zoomLevelX, zoomLevelY } = calculateZoomLevel(); + const { zoomLevelX, zoomLevelY, maxZoomLevel } = calculateZoomLevel(); + const zoom = maxZoomLevel + ? Math.max(maxZoomLevel, currentZoom.value) + : currentZoom.value; const { constrainedScrollCenterX, constrainedScrollCenterY } = - calculateConstrainedScrollCenter(); + calculateConstrainedScrollCenter(zoom); const { overscrollAllowanceX, overscrollAllowanceY } = calculateOverscrollAllowance(); const shouldAdjustForCenteredView = - zoom.value <= zoomLevelX || zoom.value <= zoomLevelY; + zoom <= zoomLevelX || zoom <= zoomLevelY; const { maxScrollX, minScrollX, maxScrollY, minScrollY } = calculateMinMaxScrollValues( shouldAdjustForCenteredView, @@ -7874,6 +7891,7 @@ class App extends React.Component { overscrollAllowanceY, constrainedScrollCenterX, constrainedScrollCenterY, + zoom, ); const { constrainedScrollX, constrainedScrollY } = constrainScrollValues( maxScrollX, diff --git a/src/types.ts b/src/types.ts index f048db85b2..4b602f7842 100644 --- a/src/types.ts +++ b/src/types.ts @@ -229,6 +229,10 @@ export type AppState = { width: number; height: number; isAnimating?: boolean; + opts?: { + viewportZoomFactor?: number; + lockZoom?: boolean; + }; } | null; };