diff --git a/tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/LogseqPortalTool.tsx b/tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/LogseqPortalTool.tsx index adf6233902..2fdbd2431d 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/LogseqPortalTool.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/LogseqPortalTool.tsx @@ -1,6 +1,5 @@ import { TLApp, TLEvents, TLTool } from '@tldraw/core' import type { TLReactEventMap } from '@tldraw/react' -import Vec from '@tldraw/vec' import { type Shape, LogseqPortalShape } from '../../shapes' import { CreatingState, IdleState } from './states' @@ -16,15 +15,7 @@ export class LogseqPortalTool extends TLTool< Shape = LogseqPortalShape - private pinchCamera(point: number[], delta: number[], zoom: number) { - const { camera } = this.app.viewport - const nextPoint = Vec.sub(camera.point, Vec.div(delta, camera.zoom)) - const p0 = Vec.sub(Vec.div(point, camera.zoom), nextPoint) - const p1 = Vec.sub(Vec.div(point, zoom), nextPoint) - this.app.setCamera(Vec.toFixed(Vec.add(nextPoint, Vec.sub(p1, p0))), zoom) - } - onPinch: TLEvents['pinch'] = info => { - this.pinchCamera(info.point, [0, 0], info.offset[0]) + this.app.viewport.pinchCamera(info.point, [0, 0], info.offset[0]) } } diff --git a/tldraw/packages/core/src/lib/TLViewport.ts b/tldraw/packages/core/src/lib/TLViewport.ts index 628d8994d6..693ef2967f 100644 --- a/tldraw/packages/core/src/lib/TLViewport.ts +++ b/tldraw/packages/core/src/lib/TLViewport.ts @@ -82,22 +82,30 @@ export class TLViewport { return Vec.mul(Vec.add(point, camera.point), camera.zoom) } - zoomIn = (): this => { - const { camera, bounds } = this - const zoom: number = Math.min(TLViewport.maxZoom, camera.zoom / ZOOM_UPDATE_FACTOR) - const center = [bounds.width / 2, bounds.height / 2] - const p0 = Vec.sub(Vec.div(center, camera.zoom), center) - const p1 = Vec.sub(Vec.div(center, zoom), center) - return this.update({ point: Vec.toFixed(Vec.add(camera.point, Vec.sub(p1, p0))), zoom }) + pinchCamera = (point: number[], delta: number[], zoom: number): this => { + const { camera } = this + zoom = Math.max(TLViewport.minZoom, Math.min(TLViewport.maxZoom, zoom)); + const nextPoint = Vec.sub(camera.point, Vec.div(delta, camera.zoom)) + const p0 = Vec.sub(Vec.div(point, camera.zoom), nextPoint) + const p1 = Vec.sub(Vec.div(point, zoom), nextPoint) + return this.update({ point: Vec.toFixed(Vec.add(nextPoint, Vec.sub(p1, p0))), zoom }) } - zoomOut = (): this => { - const { camera, bounds } = this - const zoom: number = Math.max(TLViewport.minZoom, camera.zoom * ZOOM_UPDATE_FACTOR) + setZoom = (zoom: number) => { + const { bounds } = this const center = [bounds.width / 2, bounds.height / 2] - const p0 = Vec.sub(Vec.div(center, camera.zoom), center) - const p1 = Vec.sub(Vec.div(center, zoom), center) - return this.update({ point: Vec.toFixed(Vec.add(camera.point, Vec.sub(p1, p0))), zoom }) + this.pinchCamera(center, [0, 0], zoom) + } + + zoomIn = () => { + const { camera } = this + this.setZoom(camera.zoom / ZOOM_UPDATE_FACTOR) + } + + zoomOut = () => { + const { camera, bounds } = this + this.setZoom(camera.zoom * ZOOM_UPDATE_FACTOR) + } resetZoom = (): this => { diff --git a/tldraw/packages/core/src/lib/tools/TLMoveTool/states/PinchingState.ts b/tldraw/packages/core/src/lib/tools/TLMoveTool/states/PinchingState.ts index 8c64dbb0e9..7fdb436c2d 100644 --- a/tldraw/packages/core/src/lib/tools/TLMoveTool/states/PinchingState.ts +++ b/tldraw/packages/core/src/lib/tools/TLMoveTool/states/PinchingState.ts @@ -1,4 +1,3 @@ -import { Vec } from '@tldraw/vec' import type { TLEventMap, TLEventInfo, TLEvents } from '../../../../types' import type { TLShape } from '../../../shapes' import type { TLApp } from '../../../TLApp' @@ -26,21 +25,13 @@ export class PinchingState< private prevDelta: number[] = [0, 0] - private pinchCamera(point: number[], delta: number[], zoom: number) { - const { camera } = this.app.viewport - const nextPoint = Vec.sub(camera.point, Vec.div(delta, camera.zoom)) - const p0 = Vec.sub(Vec.div(point, camera.zoom), nextPoint) - const p1 = Vec.sub(Vec.div(point, zoom), nextPoint) - this.app.setCamera(Vec.toFixed(Vec.add(nextPoint, Vec.sub(p1, p0))), zoom) - } - onEnter = (info: GestureInfo) => { this.prevDelta = info.info.delta this.origin = info.info.point } onPinch: TLEvents['pinch'] = info => { - this.pinchCamera(info.point, [0, 0], info.offset[0]) + this.app.viewport.pinchCamera(info.point, [0, 0], info.offset[0]) } onPinchEnd: TLEvents['pinch'] = () => { diff --git a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/PinchingState.ts b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/PinchingState.ts index 9be1dcc7a6..75fe027aad 100644 --- a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/PinchingState.ts +++ b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/PinchingState.ts @@ -1,4 +1,3 @@ -import { Vec } from '@tldraw/vec' import type { TLEventMap, TLEventInfo, TLEvents } from '../../../../types' import type { TLShape } from '../../../shapes' import type { TLApp } from '../../../TLApp' @@ -22,16 +21,14 @@ export class PinchingState< > extends TLToolState { static id = 'pinching' - private pinchCamera(point: number[], delta: number[], zoom: number) { + onPinch: TLEvents['pinch'] = (info, event: any) => { const { camera } = this.app.viewport - const nextPoint = Vec.sub(camera.point, Vec.div(delta, camera.zoom)) - const p0 = Vec.sub(Vec.div(point, camera.zoom), nextPoint) - const p1 = Vec.sub(Vec.div(point, zoom), nextPoint) - this.app.setCamera(Vec.toFixed(Vec.add(nextPoint, Vec.sub(p1, p0))), zoom) - } - onPinch: TLEvents['pinch'] = info => { - this.pinchCamera(info.point, [0, 0], info.offset[0]) + // Normalize the value of deltaZ from raw WheelEvent + const deltaZ = normalizeWheel(event)[2] * 0.01 + if (deltaZ === 0) return; + const zoom = camera.zoom - deltaZ * camera.zoom; + this.app.viewport.pinchCamera(info.point, [0, 0], zoom) } onPinchEnd: TLEvents['pinch'] = () => { @@ -42,3 +39,26 @@ export class PinchingState< this.tool.transition('idle') } } + +// Adapted from https://stackoverflow.com/a/13650579 +function normalizeWheel(event: WheelEvent) { + const MAX_ZOOM_STEP = 10 + const { deltaY, deltaX } = event + + let deltaZ = 0 + + if (event.ctrlKey || event.metaKey) { + const signY = Math.sign(event.deltaY) + const absDeltaY = Math.abs(event.deltaY) + + let dy = deltaY + + if (absDeltaY > MAX_ZOOM_STEP) { + dy = MAX_ZOOM_STEP * signY + } + + deltaZ = dy + } + + return [deltaX, deltaY, deltaZ] +}