All files / core/src/utilities scroll.ts

66.66% Statements 18/27
45% Branches 9/20
100% Functions 2/2
66.66% Lines 18/27

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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136                                                          16x   16x       16x             16x   16x 16x                                                           16x     16x   16x       16x 16x   16x                 16x       16x   16x     16x                   16x                         16x              
import { Events } from '../enums';
import { StackViewport, VolumeViewport } from '../RenderingEngine';
import type {
  ScrollOptions,
  EventTypes,
  IViewport,
  IVideoViewport,
  IStackViewport,
} from '../types';
import getVolumeViewportScrollInfo from './getVolumeViewportScrollInfo';
import snapFocalPointToSlice from './snapFocalPointToSlice';
import getEnabledElement from '../getEnabledElement';
import triggerEvent from './triggerEvent';
import eventTarget from '../eventTarget';
 
/**
 * It scrolls one slice in the Stack or Volume Viewport, it uses the options provided
 * to determine the slice to scroll to. For Stack Viewport, it scrolls in the 1 or -1
 * direction, for Volume Viewport, it uses the camera and focal point to determine the
 * slice to scroll to based on the spacings.
 * @param viewport - The viewport in which to scroll
 * @param options - Options to use for scrolling, including direction, invert, and volumeId
 * @returns
 */
export default function scroll(
  viewport: IViewport | IVideoViewport,
  options: ScrollOptions
): void {
  // check if viewport is disabled then throw error
  const enabledElement = getEnabledElement(viewport.element);
 
  Iif (!enabledElement) {
    throw new Error('Scroll::Viewport is not enabled (it might be disabled)');
  }
 
  Iif (
    viewport instanceof StackViewport &&
    viewport.getImageIds().length === 0
  ) {
    throw new Error('Scroll::Stack Viewport has no images');
  }
 
  const { volumeId, delta, scrollSlabs } = options;
 
  if (viewport instanceof VolumeViewport) {
    scrollVolume(viewport, volumeId, delta, scrollSlabs);
  } else E{
    const imageIdIndex = viewport.getCurrentImageIdIndex();
 
    if (
      imageIdIndex + delta >
        (viewport as IStackViewport).getImageIds().length - 1 ||
      imageIdIndex + delta < 0
    ) {
      const eventData: EventTypes.StackScrollOutOfBoundsEventDetail = {
        imageIdIndex,
        direction: delta,
      };
      triggerEvent(eventTarget, Events.STACK_SCROLL_OUT_OF_BOUNDS, eventData);
    }
 
    (viewport as IStackViewport).scroll(
      delta,
      options.debounceLoading,
      options.loop
    );
  }
}
 
export function scrollVolume(
  viewport: VolumeViewport,
  volumeId: string,
  delta: number,
  scrollSlabs = false
) {
  const useSlabThickness = scrollSlabs;
 
  const { numScrollSteps, currentStepIndex, sliceRangeInfo } =
    getVolumeViewportScrollInfo(viewport, volumeId, useSlabThickness);
 
  Iif (!sliceRangeInfo) {
    return;
  }
 
  const { sliceRange, spacingInNormalDirection, camera } = sliceRangeInfo;
  const { focalPoint, viewPlaneNormal, position } = camera;
 
  const { newFocalPoint, newPosition } = snapFocalPointToSlice(
    focalPoint,
    position,
    sliceRange,
    viewPlaneNormal,
    spacingInNormalDirection,
    delta
  );
 
  viewport.setCamera({
    focalPoint: newFocalPoint,
    position: newPosition,
  });
  viewport.render();
 
  const desiredStepIndex = currentStepIndex + delta;
 
  const VolumeScrollEventDetail: EventTypes.VolumeScrollOutOfBoundsEventDetail =
    {
      volumeId,
      viewport,
      delta,
      desiredStepIndex,
      currentStepIndex,
      numScrollSteps,
      currentImageId: viewport.getCurrentImageId(),
    };
 
  Iif (
    (desiredStepIndex > numScrollSteps || desiredStepIndex < 0) &&
    viewport.getCurrentImageId() // Check that we are in the plane of acquisition
  ) {
    // One common use case of this trigger might be to load the next
    // volume in a time series or the next segment of a partially loaded volume.
 
    triggerEvent(
      eventTarget,
      Events.VOLUME_VIEWPORT_SCROLL_OUT_OF_BOUNDS,
      VolumeScrollEventDetail
    );
  } else {
    triggerEvent(
      eventTarget,
      Events.VOLUME_VIEWPORT_SCROLL,
      VolumeScrollEventDetail
    );
  }
}