diff --git a/src/component/2d/BrushTracker2D.tsx b/src/component/2d/BrushTracker2D.tsx index b443621344..5270055f3f 100644 --- a/src/component/2d/BrushTracker2D.tsx +++ b/src/component/2d/BrushTracker2D.tsx @@ -15,6 +15,7 @@ import { import { useChartData } from '../context/ChartContext.js'; import { useDispatch } from '../context/DispatchContext.js'; import { useMapKeyModifiers } from '../context/KeyModifierContext.js'; +import { usePreferences } from '../context/PreferencesContext.js'; import { options } from '../toolbar/ToolTypes.js'; import { @@ -23,7 +24,6 @@ import { } from './utilities/DimensionLayout.js'; import type { Layout } from './utilities/DimensionLayout.js'; import { useScale2DX, useScale2DY } from './utilities/scale.js'; -import { usePreferences } from '../context/PreferencesContext.js'; function usePixelToPPMConverter() { const scaleX = useScale2DX(); diff --git a/src/component/EventsTrackers/BrushTracker.tsx b/src/component/EventsTrackers/BrushTracker.tsx index 78d913bfa1..5c66dbc7e4 100644 --- a/src/component/EventsTrackers/BrushTracker.tsx +++ b/src/component/EventsTrackers/BrushTracker.tsx @@ -91,9 +91,9 @@ export type OnClick = (element: ClickOptions) => void; export type { OnClick as OnDoubleClick }; export type ZoomOptions = Pick< React.WheelEvent, - 'deltaY' | 'shiftKey' | 'deltaMode' | 'altKey' + 'deltaY' | 'shiftKey' | 'deltaMode' | 'altKey' | 'deltaX' | 'ctrlKey' > & - Position & { invertScroll?: boolean }; + Position & { invertScroll?: boolean; isBidirectionalZoom: boolean }; export type OnZoom = (options: ZoomOptions) => void; export type OnBrush = (state: BrushTrackerData) => void; @@ -127,6 +127,9 @@ export function BrushTracker({ const timeoutRef = useRef(null); const lastPointRef = useRef(0); const isDraggingRef = useRef(false); + const boundingRectRef = useRef(null); + const startPositionRef = useRef({ x: 0, y: 0 }); + const lastRef = useRef({ x: 0, y: 0 }); const clickHandler = useCallback( (event: React.MouseEvent, targetElement: Element) => { @@ -168,33 +171,68 @@ export function BrushTracker({ if (noPropagation) { event.stopPropagation(); } - dispatch({ - type: 'DOWN', - payload: { - mouseButton: MouseButtons[event.button], - shiftKey: event.shiftKey, - altKey: event.altKey, - screenX: event.screenX, - screenY: event.screenY, - clientX: event.clientX, - clientY: event.clientY, - boundingRect: event.currentTarget.getBoundingClientRect(), - }, - }); + + const boundingRect = event.currentTarget.getBoundingClientRect(); + boundingRectRef.current = boundingRect; + startPositionRef.current = { + x: event.clientX - boundingRect.x, + y: event.clientY - boundingRect.y, + }; + + if (!event.ctrlKey) { + dispatch({ + type: 'DOWN', + payload: { + mouseButton: MouseButtons[event.button], + shiftKey: event.shiftKey, + altKey: event.altKey, + screenX: event.screenX, + screenY: event.screenY, + clientX: event.clientX, + clientY: event.clientY, + boundingRect, + }, + }); + } } function moveCallback(event: PointerEvent) { isDraggingRef.current = true; // set flag to true to skip click event if the user dragged the mouse - - dispatch({ - type: 'MOVE', - payload: { - screenX: event.screenX, - screenY: event.screenY, - clientX: event.clientX, - clientY: event.clientY, - }, - }); + const { clientX, clientY, shiftKey, altKey, ctrlKey } = event; + + if (event.ctrlKey) { + if (boundingRectRef.current) { + const boundingRect = boundingRectRef.current; + const x = clientX - boundingRect.x; + const y = clientY - boundingRect.y; + + const deltaX = clientX - boundingRect.x - lastRef.current.x; + const deltaY = clientY - boundingRect.y - lastRef.current.y; + + lastRef.current = { x, y }; + onZoom({ + deltaY, + deltaX, + shiftKey, + altKey, + x: startPositionRef.current.x, + y: startPositionRef.current.y, + ctrlKey, + deltaMode: 0, + isBidirectionalZoom: true, + }); + } + } else { + dispatch({ + type: 'MOVE', + payload: { + screenX: event.screenX, + screenY: event.screenY, + clientX: event.clientX, + clientY: event.clientY, + }, + }); + } } function upCallback() { @@ -218,7 +256,7 @@ export function BrushTracker({ return false; }, - [clickHandler, noPropagation], + [clickHandler, noPropagation, onZoom], ); const handleMouseWheel = useCallback( @@ -227,8 +265,18 @@ export function BrushTracker({ const x = event.clientX - boundingRect.x; const y = event.clientY - boundingRect.y; - const { deltaY, deltaX, shiftKey, altKey, deltaMode } = event; - onZoom({ deltaY: deltaY || deltaX, shiftKey, altKey, deltaMode, x, y }); + const { deltaY, deltaX, shiftKey, altKey, ctrlKey, deltaMode } = event; + onZoom({ + deltaY, + deltaX, + shiftKey, + altKey, + ctrlKey, + deltaMode, + x, + y, + isBidirectionalZoom: false, + }); }, [onZoom], ); @@ -255,6 +303,11 @@ export function BrushTracker({ return (
{ + if (e.ctrlKey && e.button === 0) { + e.preventDefault(); + } + }} className={className} style={{ ...style, touchAction: 'none' }} onPointerDown={pointerDownHandler} diff --git a/src/component/reducer/actions/InsetActions.ts b/src/component/reducer/actions/InsetActions.ts index 84784145ac..c25e19b1e8 100644 --- a/src/component/reducer/actions/InsetActions.ts +++ b/src/component/reducer/actions/InsetActions.ts @@ -261,8 +261,9 @@ function zoomWithScroll(draft: Draft, options: ZoomWithScroll1DOptions) { const { originDomain, mode } = draft; const scaleX = getXScale(inset, { baseSize, mode }); + const { invertScroll, deltaX, deltaY } = zoomOptions; - const scaleRatio = toScaleRatio(zoomOptions); + const scaleRatio = toScaleRatio({ delta: deltaY || deltaX, invertScroll }); const { x } = zoomOptions; const domain = zoomIdentity @@ -280,7 +281,7 @@ function handleInsetZoom(draft: Draft, action: ZoomInsetAction) { const { toolOptions: { selectedTool }, } = draft; - const { altKey, shiftKey } = options; + const { altKey, shiftKey, deltaX, deltaY, invertScroll } = options; const inset = getInset(draft, insetKey); if (!inset) return; @@ -294,7 +295,7 @@ function handleInsetZoom(draft: Draft, action: ZoomInsetAction) { if (altKey) { // rescale the integral in ranges and integrals const { view } = inset; - const scaleRatio = toScaleRatio(options); + const scaleRatio = toScaleRatio({ delta: deltaY | deltaX, invertScroll }); if (selectedTool === 'rangePicking') { view.ranges.integralsScaleRatio *= scaleRatio; diff --git a/src/component/reducer/actions/ToolsActions.ts b/src/component/reducer/actions/ToolsActions.ts index 4ae56cb47a..41e65030e0 100644 --- a/src/component/reducer/actions/ToolsActions.ts +++ b/src/component/reducer/actions/ToolsActions.ts @@ -318,6 +318,7 @@ function zoomWithScroll( options: ZoomWithScroll1DOptions | ZoomWithScroll2DOptions, ) { const { zoomOptions, direction = 'Horizontal', dimension } = options; + const { x, deltaX, invertScroll } = zoomOptions; let scaleX; let scaleY; @@ -328,10 +329,9 @@ function zoomWithScroll( scaleX = get2DXScale(draft); scaleY = get2DYScale(draft); } - const scaleRatio = toScaleRatio(zoomOptions); + const scaleRatio = toScaleRatio({ delta: deltaX, invertScroll }); if (direction === 'Both' || direction === 'Horizontal') { - const { x } = zoomOptions; const domain = zoomIdentity .translate(x, 0) .scale(scaleRatio) @@ -373,8 +373,10 @@ function handleZoom(draft: Draft, action: ZoomAction) { yDomains, toolOptions: { selectedTool }, } = draft; - const scaleRatio = toScaleRatio(options); - const { altKey, shiftKey } = options; + const { altKey, shiftKey, invertScroll, deltaY, isBidirectionalZoom } = + options; + + const scaleRatio = toScaleRatio({ delta: deltaY, invertScroll }); switch (displayerMode) { case '2D': { @@ -387,7 +389,7 @@ function handleZoom(draft: Draft, action: ZoomAction) { } //zoom in/out in 2d - if (shiftKey) { + if (shiftKey || isBidirectionalZoom) { zoomWithScroll(draft, { zoomOptions: options, dimension: '2D', @@ -414,9 +416,11 @@ function handleZoom(draft: Draft, action: ZoomAction) { const activeSpectra = getActiveSpectra(draft); // Horizontal zoom in/out 1d spectra by mouse wheel - if (shiftKey) { + if (shiftKey || isBidirectionalZoom) { zoomWithScroll(draft, { zoomOptions: options, dimension: '1D' }); - return; + if (!isBidirectionalZoom) { + return; + } } // rescale the integral in ranges and integrals diff --git a/src/component/reducer/helper/Zoom1DManager.ts b/src/component/reducer/helper/Zoom1DManager.ts index bd2c45f338..5a531baa05 100644 --- a/src/component/reducer/helper/Zoom1DManager.ts +++ b/src/component/reducer/helper/Zoom1DManager.ts @@ -16,15 +16,15 @@ export const ZOOM_TYPES = { export type ZoomType = keyof typeof ZOOM_TYPES; function toScaleRatio( - options: ZoomOptions, + options: { invertScroll?: boolean; delta: number }, zoomOptions: ScaleRationOptions = {}, ) { const { invertScroll = false } = options; const { factor = 1 } = zoomOptions; - const deltaY = - Math.abs(options.deltaY) < 100 ? options.deltaY * 100 : options.deltaY; - const delta = deltaY * (invertScroll ? -0.001 : 0.001) * factor; + const normalizeDelta = + Math.abs(options.delta) < 100 ? options.delta * 100 : options.delta; + const delta = normalizeDelta * (invertScroll ? -0.001 : 0.001) * factor; const ratio = delta < 0 ? -1 / (delta - 1) : 1 + delta; return ratio; @@ -38,7 +38,8 @@ function wheelZoom( domain: number[], scaleOptions: ScaleRationOptions = {}, ): number[] { - const ratio = toScaleRatio(options, scaleOptions); + const { deltaY, invertScroll } = options; + const ratio = toScaleRatio({ delta: deltaY, invertScroll }, scaleOptions); const [min, max] = domain; return [min * ratio, max * ratio]; } diff --git a/src/component/reducer/preferences/actions/matrixGeneration.ts b/src/component/reducer/preferences/actions/matrixGeneration.ts index 93f41ad6da..286a911fea 100644 --- a/src/component/reducer/preferences/actions/matrixGeneration.ts +++ b/src/component/reducer/preferences/actions/matrixGeneration.ts @@ -148,8 +148,8 @@ function changeMatrixGenerationScale( const matrixGeneration = getMatrixGenerationPanelOptions(draft, nucleus); if (!matrixGeneration) return; - - const scaleRatio = toScaleRatio(zoomOptions); + const { deltaY, invertScroll } = zoomOptions; + const scaleRatio = toScaleRatio({ delta: deltaY, invertScroll }); matrixGeneration.scaleRatio *= scaleRatio; } diff --git a/src/component/toolbar/ToolBar.tsx b/src/component/toolbar/ToolBar.tsx index 4e335aedc5..53956b802b 100644 --- a/src/component/toolbar/ToolBar.tsx +++ b/src/component/toolbar/ToolBar.tsx @@ -221,8 +221,10 @@ export default function ToolBar() { subTitles: [ { title: 'Vertical', shortcuts: ['Scroll wheel'] }, { title: 'Horizontal', shortcuts: ['⇧', 'Scroll wheel'] }, + { title: 'Horizontal and Vertical', shortcuts: ['CTRL', 'drag'] }, { title: 'Pan', shortcuts: ['Right button'] }, ], + style: { minWidth: '300px' }, }, icon: , },