MasatoMakino/pixijs-basic-scrollbar

View on GitHub
src/SliderViewUtil.ts

Summary

Maintainability
A
1 hr
Test Coverage
B
85%
import {
  FederatedPointerEvent,
  Point,
  Rectangle,
  Container,
  Bounds,
  EventEmitter,
  ContainerEvents,
} from "pixi.js";
import { SliderView } from "./index.js";

export class SliderViewUtil {
  /**
   * スライダーの座標から、スライダーの割合を取得する
   */
  public static convertPixelToRate(
    pixel: number,
    max: number,
    min: number,
  ): number {
    if (max <= min) {
      return 0.0;
    }
    const rate: number = ((pixel - min) / (max - min)) * SliderView.MAX_RATE;
    return SliderViewUtil.clamp(rate, SliderView.MAX_RATE, 0.0);
  }

  public static convertRateToPixel(
    rate: number,
    max: number,
    min: number,
  ): number {
    const pix: number = ((max - min) * rate) / SliderView.MAX_RATE + min;
    return SliderViewUtil.clamp(pix, max, min);
  }

  /**
   * ディスプレイオブジェクトからスクロール方向の座標値を取り出す
   * @return displayObjの座標値。単位ピクセル
   */
  public static getPosition(
    displayObj: Container | Point,
    isHorizontal: boolean,
  ): number {
    if (isHorizontal) {
      return displayObj.x;
    }
    return displayObj.y;
  }

  public static getPointerEventPosition(
    e: PointerEvent,
    isHorizontal: boolean,
  ) {
    if (e instanceof FederatedPointerEvent) {
      return SliderViewUtil.getPosition(e.global, isHorizontal);
    } else {
      return SliderViewUtil.getPosition(
        new Point(e.offsetX, e.offsetY),
        isHorizontal,
      );
    }
  }

  /**
   * ディスプレイオブジェクトにスクロール方向の座標値を設定する
   */
  public static setPosition(
    displayObj: Container,
    isHorizontal: boolean,
    position: number,
  ): void {
    if (!displayObj) return;

    if (isHorizontal) {
      displayObj.x = position;
    } else {
      displayObj.y = position;
    }
  }

  /**
   * スクロール方向の高さ、もしくは幅を取得する。単位ピクセル
   */
  public static getSize(displayObj: Container, isHorizontal: boolean): number {
    const size = SliderViewUtil.getContentsBounds(displayObj);
    if (isHorizontal) {
      return size.width * displayObj.scale.x;
    } else {
      return size.height * displayObj.scale.y;
    }
  }

  /**
   * スクロール方向の高さ、もしくは幅を設定する。
   * @param displayObj
   * @param isHorizontal
   * @param amount width or height, range : 0 ~ displayObj.size.width or height, unit : px
   */
  public static setSize(
    displayObj: Container,
    isHorizontal: boolean,
    amount: number,
  ): void {
    const size = SliderViewUtil.getContentsBounds(displayObj);

    if (isHorizontal) {
      displayObj.scale.x = amount / size.width;
    } else {
      displayObj.scale.y = amount / size.height;
    }
  }

  public static clamp(num: number, max: number, min: number): number {
    num = Math.max(num, min);
    num = Math.min(num, max);
    return num;
  }

  public static getContentsBounds(displayObj: Container): Bounds | Rectangle {
    if (displayObj.boundsArea) {
      return displayObj.boundsArea;
    }
    if (displayObj.hitArea) {
      return displayObj.hitArea as Rectangle;
    }
    return displayObj.getLocalBounds();
  }

  static getRootContainer(
    canvas: HTMLCanvasElement | undefined,
    button: Container,
  ): Container | HTMLCanvasElement {
    if (canvas) {
      return canvas;
    }

    let parent = button.parent;
    while (parent.parent) {
      parent = parent.parent;
    }
    return parent;
  }

  static addChildParts(parent: Container, obj?: Container): void {
    if (!obj) return;
    obj.parent?.removeChild(obj);
    parent.addChild(obj);
  }

  static getPointerLocalPosition(
    displayObj: Container,
    isHorizontal: boolean,
    dragStartPos: Point,
    evt: PointerEvent,
  ): number {
    const getLocalPos = () => {
      if (evt instanceof FederatedPointerEvent) {
        return displayObj.toLocal(evt.global);
      }
      return displayObj.toLocal(new Point(evt.offsetX, evt.offsetY));
    };
    const localPos = getLocalPos();

    if (isHorizontal) {
      return localPos.x - dragStartPos.x;
    } else {
      return localPos.y - dragStartPos.y;
    }
  }

  /**
   * リスナー関数を切り替える。
   *
   * @param shouldAdd リスナーを有効にするか否か
   * @param target ターゲットオブジェクト
   * @param event イベント名
   * @param listener リスナー関数
   * @returns
   */
  static toggleEventListener(
    shouldAdd: boolean,
    target: Container | HTMLCanvasElement,
    event: keyof ContainerEvents,
    listener: EventEmitter.ListenerFn,
  ): void {
    if (target instanceof HTMLCanvasElement) {
      const method = shouldAdd ? "addEventListener" : "removeEventListener";
      target[method](event as string, listener);
      return;
    }
    const pixiMethod = shouldAdd ? "on" : "off";
    target[pixiMethod](event, listener);
  }

  static addEventListenerToTarget(
    target: Container | HTMLCanvasElement,
    event: keyof ContainerEvents,
    listener: EventEmitter.ListenerFn,
  ) {
    this.toggleEventListener(true, target, event, listener);
  }

  static removeEventListenerFromTarget(
    target: Container | HTMLCanvasElement,
    event: keyof ContainerEvents,
    listener: EventEmitter.ListenerFn,
  ) {
    this.toggleEventListener(false, target, event, listener);
  }
}