src_tools_scrollWheel.js

import {getLayerDetailsFromEvent} from '../gui/layerGroup';

// doc imports
/* eslint-disable no-unused-vars */
import {App} from '../app/application';
/* eslint-enable no-unused-vars */

/**
 * Get a normalised spin speed in the Y direction to try to support
 * trackpads (small and large deltaY) and mouse wheel (large deltaY).
 * Should return 1 or -1 for a single mouse wheel tick.
 *
 * @param {object} event The wheel event.
 * @returns {number} The normalised spin Y.
 */
function getSpinY(event) {
  // (notes of 03/2024)

  // firefox seems to change the value of deltaY
  // if you ask for deltaMode before (?????)

  // deltaY (for a single mouse wheel tick):
  // - chrome: [linux] 120, [mac]: 4
  // - firefox: [linux] 132, [mac]: 16

  // wheelDelta (for a single mouse wheel tick):
  // - chrome: [linux] 120, [mac]: 240
  // - firefox: [linux] 120, [mac]: 48

  // -> using wheelDelta for mouse wheel detection as
  //    it is consistently larger than trackpad scroll

  // wheelDeltaY and deltaY do not go in the same direction,
  // using -deltaY so that they do...

  if (typeof event.wheelDeltaY === 'undefined') {
    //logger.warn('No wheel delta, scroll could be tricky...);
    return -event.deltaY;
  } else {
    const threshold = 45;
    if (event.wheelDeltaY > threshold) {
      return 1;
    } else if (event.wheelDeltaY < -threshold) {
      return -1;
    } else {
      return -event.deltaY / 60;
    }
  }
}

/**
 * Class to sum wheel events and know if that sum
 * corresponds to a 'tick'.
 */
class ScrollSum {
  /**
   * The scroll sum.
   *
   * @type {number}
   */
  #sum = 0;

  /**
   * Get the scroll sum.
   *
   * @returns {number} The scroll sum.
   */
  getSum() {
    return this.#sum;
  }

  /**
   * Add scroll.
   *
   * @param {object} event The wheel event.
   */
  add(event) {
    this.#sum += getSpinY(event);
  }

  /**
   * Clear the scroll sum.
   */
  clear() {
    this.#sum = 0;
  }

  /**
   * Does the accumulated scroll correspond to a 'tick'.
   *
   * @returns {boolean} True if the sum corresponds to a 'tick'.
   */
  isTick() {
    return Math.abs(this.#sum) >= 1;
  }
}

/**
 * Scroll wheel class: provides a wheel event handler
 *   that scroll the corresponding data.
 */
export class ScrollWheel {
  /**
   * Associated app.
   *
   * @type {App}
   */
  #app;

  /**
   * Accumulated scroll.
   *
   * @type {ScrollSum}
   */
  #scrollSum = new ScrollSum();

  /**
   * @param {App} app The associated application.
   */
  constructor(app) {
    this.#app = app;
  }

  /**
   * Handle mouse wheel event.
   *
   * @param {WheelEvent} event The mouse wheel event.
   */
  wheel(event) {
    this.#scrollSum.add(event);
    const up = this.#scrollSum.getSum() >= 0;

    // exit if no tick
    if (!this.#scrollSum.isTick()) {
      return;
    } else {
      this.#scrollSum.clear();
    }

    // prevent default page scroll
    event.preventDefault();

    const layerDetails = getLayerDetailsFromEvent(event);
    const layerGroup = this.#app.getLayerGroupByDivId(layerDetails.groupDivId);
    const viewController =
      layerGroup.getActiveViewLayer().getViewController();
    let newPosition;
    if (layerGroup.canScroll()) {
      if (up) {
        newPosition = viewController.getIncrementScrollPosition();
      } else {
        newPosition = viewController.getDecrementScrollPosition();
      }
    } else if (layerGroup.moreThanOne(3)) {
      if (up) {
        newPosition = viewController.getIncrementPosition(3);
      } else {
        newPosition = viewController.getDecrementPosition(3);
      }
    }

    // set all layers if at least one can be set
    if (typeof newPosition !== 'undefined' &&
      layerGroup.isPositionInBounds(newPosition)) {
      viewController.setCurrentPosition(newPosition);
    }
  }

} // ScrollWheel class