/** * Wheel controller helper functions for mouse wheel/trackpad event handling. * This module provides pure functions for scroll wheel interaction logic. * * Related to: Mouse wheel scrolling, zooming, ROI resizing, segmentation threshold */ import { DRAG_MODE } from '../../nvdocument.js' /** * Parameters for calculating scroll amount */ export interface CalculateScrollAmountParams { deltaY: number invertScrollDirection: boolean } /** * Parameters for checking if ROI selection resize is valid */ export interface IsValidRoiResizeParams { dragMode: DRAG_MODE dragStart: number[] dragEnd: number[] } /** * Parameters for calculating updated ROI selection bounds */ export interface UpdateRoiSelectionParams { dragStart: number[] dragEnd: number[] delta: number } /** * Result of ROI selection update */ export interface RoiSelectionResult { newDragStart: number[] newDragEnd: number[] } /** * Parameters for calculating zoom values */ export interface CalculateZoomParams { currentZoom: number scrollAmount: number } /** * Result of zoom calculation */ export interface ZoomResult { newZoom: number zoomChange: number } /** * Parameters for calculating pan offset after zoom */ export interface CalculatePanOffsetParams { currentPan: number[] zoomChange: number crosshairMM: number[] } /** * Parameters for click-to-segment threshold adjustment */ export interface AdjustSegmentThresholdParams { currentPercent: number scrollAmount: number } /** * Parameters for determining wheel action */ export interface DetermineWheelActionParams { thumbnailVisible: boolean mosaicStringLength: number eventInBounds: boolean hasBounds: boolean } /** * Result of wheel action determination */ export interface WheelActionResult { shouldProcess: boolean showBoundsBorder: boolean } /** * Parameters for checking if pan/zoom mode scroll should apply */ export interface ShouldZoomParams { dragMode: DRAG_MODE isInRenderTile: boolean } /** * Calculate the normalized scroll amount from wheel event * @param params - Scroll calculation parameters * @returns Normalized scroll amount (-0.01 or 0.01, possibly inverted) */ export function calculateScrollAmount(params: CalculateScrollAmountParams): number { const { deltaY, invertScrollDirection } = params let scrollAmount = deltaY < 0 ? -0.01 : 0.01 if (invertScrollDirection) { scrollAmount = -scrollAmount } return scrollAmount } /** * Check if the current drag state allows ROI selection resizing * @param params - ROI resize validation parameters * @returns True if ROI resize should proceed */ export function isValidRoiResize(params: IsValidRoiResizeParams): boolean { const { dragMode, dragStart, dragEnd } = params if (dragMode !== DRAG_MODE.roiSelection) { return false } const dragStartSum = dragStart.reduce((a, b) => a + b, 0) const dragEndSum = dragEnd.reduce((a, b) => a + b, 0) return dragStartSum > 0 && dragEndSum > 0 } /** * Calculate updated ROI selection bounds based on scroll delta * @param params - ROI selection update parameters * @returns New drag start and end positions */ export function updateRoiSelection(params: UpdateRoiSelectionParams): RoiSelectionResult { const { dragStart, dragEnd, delta } = params const newDragStart = [...dragStart] const newDragEnd = [...dragEnd] // Update X bounds if (dragStart[0] < dragEnd[0]) { newDragStart[0] = dragStart[0] - delta newDragEnd[0] = dragEnd[0] + delta } else { newDragStart[0] = dragStart[0] + delta newDragEnd[0] = dragEnd[0] - delta } // Update Y bounds if (dragStart[1] < dragEnd[1]) { newDragStart[1] = dragStart[1] - delta newDragEnd[1] = dragEnd[1] + delta } else { newDragStart[1] = dragStart[1] + delta newDragEnd[1] = dragEnd[1] - delta } return { newDragStart, newDragEnd } } /** * Calculate the scroll delta direction for ROI resizing * @param deltaY - Wheel event deltaY * @returns 1 for scroll down (grow), -1 for scroll up (shrink) */ export function getRoiScrollDelta(deltaY: number): number { return deltaY > 0 ? 1 : -1 } /** * Calculate new zoom level and change from scroll * @param params - Zoom calculation parameters * @returns New zoom value and the change amount */ export function calculateZoom(params: CalculateZoomParams): ZoomResult { const { currentZoom, scrollAmount } = params const zoomDirection = scrollAmount < 0 ? 1 : -1 let newZoom = currentZoom * (1.0 + 10 * (0.01 * zoomDirection)) newZoom = Math.round(newZoom * 10) / 10 const zoomChange = currentZoom - newZoom return { newZoom, zoomChange } } /** * Calculate new pan offset after zoom to keep crosshair in place * @param params - Pan offset calculation parameters * @returns New pan values [x, y, z] */ export function calculatePanOffsetAfterZoom(params: CalculatePanOffsetParams): number[] { const { currentPan, zoomChange, crosshairMM } = params return [currentPan[0] + zoomChange * crosshairMM[0], currentPan[1] + zoomChange * crosshairMM[1], currentPan[2] + zoomChange * crosshairMM[2]] } /** * Adjust click-to-segment threshold based on scroll direction * @param params - Threshold adjustment parameters * @returns New threshold percent clamped to [0, 1] */ export function adjustSegmentThreshold(params: AdjustSegmentThresholdParams): number { const { currentPercent, scrollAmount } = params let newPercent = currentPercent if (scrollAmount < 0) { newPercent -= 0.01 newPercent = Math.max(newPercent, 0) } else { newPercent += 0.01 newPercent = Math.min(newPercent, 1) } return newPercent } /** * Determine if wheel event should be processed based on current state * @param params - Wheel action determination parameters * @returns Whether to process and updated bounds border state */ export function determineWheelAction(params: DetermineWheelActionParams): WheelActionResult { const { thumbnailVisible, mosaicStringLength, eventInBounds, hasBounds } = params // Don't process if thumbnail is visible or in mosaic mode if (thumbnailVisible || mosaicStringLength > 0) { return { shouldProcess: false, showBoundsBorder: false } } // Don't process if event is outside bounds if (!eventInBounds) { return { shouldProcess: false, showBoundsBorder: false } } return { shouldProcess: true, showBoundsBorder: hasBounds } } /** * Check if zoom should be applied based on drag mode and position * @param params - Zoom check parameters * @returns True if zoom should be applied */ export function shouldApplyZoom(params: ShouldZoomParams): boolean { const { dragMode, isInRenderTile } = params return dragMode === DRAG_MODE.pan && !isInRenderTile } /** * Get mouse position relative to canvas from wheel event * @param clientX - Event clientX * @param clientY - Event clientY * @param canvasRect - Canvas bounding rect * @returns Position [x, y] relative to canvas */ export function getWheelEventPosition(clientX: number, clientY: number, canvasRect: DOMRect): [number, number] { return [clientX - canvasRect.left, clientY - canvasRect.top] }