All files / tools/src/eventDispatchers/shared releaseToolForMultiTouchGesture.ts

0% Statements 0/19
0% Branches 0/12
0% Functions 0/1
0% Lines 0/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81                                                                                                                                                                 
import { state } from '../../store/state';
import { getToolGroupForViewport } from '../../store/ToolGroupManager';
import type { EventTypes } from '../../types';
 
/**
 * Releases a one-finger tool gesture that a second finger has reclassified
 * into a viewport manipulation (pinch zoom, multi-finger scroll).
 *
 * The tool that owns the draw loop has set `state.isInteractingWithTool` in
 * its `_activateDraw`/`_activateModify`, and every touch dispatcher returns
 * early while that flag is set. The gesture therefore never re-resolves
 * against the new finger count, which is why pan/zoom appears dead as soon as
 * an annotation tool is active - the binding for two fingers is correct, the
 * event just never reaches it.
 *
 * Rather than teaching each of the ~29 tools that set the flag to detect the
 * extra finger, the loop is released centrally through
 * `AnnotationTool.cancel()`. That method is abstract, so every annotation tool
 * implements it (it is what the Escape key already uses), and it no-ops unless
 * the tool is mid-draw - so calling it across the tool group is safe and needs
 * no bookkeeping about which tool is currently interacting.
 *
 * Tools that implement their own semantics for extra touch points declare
 * {@link BaseTool.handlesMultiTouchGestures} and are never cancelled: the
 * multi-part contour tools must keep the points already placed. For those the
 * interaction guard is bypassed instead, so the manipulation tool still
 * receives the gesture while the drawing tool keeps its in-progress state and
 * ignores the extra finger on its own.
 *
 * Note this deliberately reuses cancel() semantics rather than inventing new
 * ones: a tool that commits a new annotation on Escape also commits it here.
 *
 * @param evt - The normalized touch drag event.
 * @returns true when the caller should dispatch this event even though
 * `state.isInteractingWithTool` is set.
 */
export default function releaseToolForMultiTouchGesture(
  evt: EventTypes.TouchDragEventType
): boolean {
  if (!state.isInteractingWithTool) {
    return false;
  }
 
  // Single-finger drags are the normal tool path and must not be disturbed.
  if (!(evt.detail.currentPointsList?.length > 1)) {
    return false;
  }
 
  const { renderingEngineId, viewportId, element } = evt.detail;
  const toolGroup = getToolGroupForViewport(viewportId, renderingEngineId);
 
  if (!toolGroup) {
    return false;
  }
 
  let bypassInteractionGuard = false;
 
  for (const toolName of Object.keys(toolGroup.toolOptions)) {
    const tool = toolGroup.getToolInstance(toolName);
 
    if (!tool) {
      continue;
    }
 
    if (tool.handlesMultiTouchGestures) {
      // Only the tool actually mid-interaction may unblock the dispatcher;
      // otherwise merely having a contour tool in the group would bypass the
      // guard for every other tool's gestures.
      if (tool.isDrawing) {
        bypassInteractionGuard = true;
      }
      continue;
    }
 
    // Safe unconditionally: cancel() returns immediately unless mid-draw.
    tool.cancel?.(element);
  }
 
  return bypassInteractionGuard;
}