Sfoglia il codice sorgente

Fixes for mobile touch, implementation for scroll parents, using resizeobserver where applicable

Justin Litten 4 anni fa
parent
commit
4bfab60f44
2 ha cambiato i file con 160 aggiunte e 24 eliminazioni
  1. 1 0
      package.json
  2. 159 24
      src/Display/WebDisplayInteractionManager.ts

+ 1 - 0
package.json

@@ -76,6 +76,7 @@
     "@types/chai": "^4.2.11",
     "@types/chai": "^4.2.11",
     "@types/mocha": "^7.0.2",
     "@types/mocha": "^7.0.2",
     "@types/node": "^14.0.9",
     "@types/node": "^14.0.9",
+    "@types/resize-observer-browser": "^0.1.5",
     "@typescript-eslint/eslint-plugin": "^4.14.0",
     "@typescript-eslint/eslint-plugin": "^4.14.0",
     "@typescript-eslint/parser": "^4.14.0",
     "@typescript-eslint/parser": "^4.14.0",
     "canvas": "^2.6.1",
     "canvas": "^2.6.1",

+ 159 - 24
src/Display/WebDisplayInteractionManager.ts

@@ -1,11 +1,18 @@
 import { AbstractDisplayInteractionManager } from "./AbstractDisplayInteractionManager";
 import { AbstractDisplayInteractionManager } from "./AbstractDisplayInteractionManager";
 import { PointF2D } from "../Common/DataObjects/PointF2D";
 import { PointF2D } from "../Common/DataObjects/PointF2D";
+import { Dictionary } from "typescript-collections";
 
 
 export class WebDisplayInteractionManager extends AbstractDisplayInteractionManager {
 export class WebDisplayInteractionManager extends AbstractDisplayInteractionManager {
     protected isTouchDevice: boolean = false;
     protected isTouchDevice: boolean = false;
     protected osmdSheetMusicContainer: HTMLElement;
     protected osmdSheetMusicContainer: HTMLElement;
     protected fullOffsetLeft: number = 0;
     protected fullOffsetLeft: number = 0;
     protected fullOffsetTop: number = 0;
     protected fullOffsetTop: number = 0;
+    protected fullScrollTop: number = 0;
+    protected fullScrollLeft: number = 0;
+    //Using map instead of collections dictionary because map supports using objects as keys properly
+    protected parentScrollMap: Map<HTMLElement, number[]> = new Map<HTMLElement, number[]>();
+    protected scrollCallbackMap: Map<HTMLElement, (this: HTMLElement, ev: Event) => any> =
+                                 new Map<HTMLElement, (this: HTMLElement, ev: Event) => any>();
 
 
     constructor(osmdContainer: HTMLElement) {
     constructor(osmdContainer: HTMLElement) {
         super();
         super();
@@ -14,22 +21,136 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
         this.listenForInteractions();
         this.listenForInteractions();
     }
     }
 
 
+    public get FullOffsetTop(): number {
+        return this.fullOffsetTop;
+    }
+
+    public get FullScrollTop(): number {
+        return this.fullScrollTop;
+    }
+
+    public get FullOffsetLeft(): number {
+        return this.fullOffsetLeft;
+    }
+
+    public get FullScrollLeft(): number {
+        return this.fullScrollLeft;
+    }
+
+    protected timeout: NodeJS.Timeout = undefined;
+
+    protected static resizeCallback(entries: ResizeObserverEntry[]|HTMLElement[], self: WebDisplayInteractionManager): void {
+        //debounce resize callback
+        clearTimeout(self.timeout);
+        self.timeout = setTimeout(()=> {
+            self.fullOffsetLeft = 0;
+            self.fullOffsetTop = 0;
+            let nextOffsetParent: HTMLElement = self.osmdSheetMusicContainer;
+            while (nextOffsetParent) {
+                self.fullOffsetLeft += nextOffsetParent.offsetLeft;
+                self.fullOffsetTop += nextOffsetParent.offsetTop;
+                nextOffsetParent = nextOffsetParent.offsetParent as HTMLElement;
+            }
+            self.resizeEventListener();
+            self.deregisterScrollOffsets();
+            self.registerScrollOffsets();
+        }, 500);
+    }
+
+    protected registerScrollOffsets(): void {
+        let nextScrollParent: HTMLElement = this.osmdSheetMusicContainer;
+        this.fullScrollTop = 0;
+        this.fullScrollLeft = 0;
+        const self: WebDisplayInteractionManager = this;
+        while(nextScrollParent && nextScrollParent !== document.documentElement){
+            this.parentScrollMap.set(nextScrollParent, [nextScrollParent.scrollTop, nextScrollParent.scrollLeft]);
+            this.fullScrollLeft += nextScrollParent.scrollLeft;
+            this.fullScrollTop += nextScrollParent.scrollTop;
+            if(nextScrollParent.scrollHeight > nextScrollParent.clientHeight){
+                const nextScrollCallback: (this: HTMLElement, ev: Event) => any = function(scrollEvent: Event): void{
+                    //@ts-ignore
+                    const currentScroll: number[] = self.parentScrollMap.get(this);
+                    const currentScrollTop: number = currentScroll[0];
+                    const currentScrollLeft: number = currentScroll[1];
+                    //@ts-ignore
+                    self.fullScrollTop = self.fullScrollTop - currentScrollTop + this.scrollTop;
+                    //@ts-ignore
+                    self.fullScrollLeft = self.fullScrollLeft - currentScrollLeft + this.scrollLeft;
+                    //@ts-ignore
+                    self.parentScrollMap.set(this, [this.scrollTop, this.scrollLeft]);
+                };
+
+
+                this.scrollCallbackMap.set(nextScrollParent, nextScrollCallback);
+                nextScrollParent.addEventListener("scroll", nextScrollCallback);
+            }
+            nextScrollParent = nextScrollParent.parentElement;
+        }
+    }
+
+    protected deregisterScrollOffsets(): void {
+        for(const key of this.scrollCallbackMap.keys()){
+            key.removeEventListener("scroll", this.scrollCallbackMap.get(key));
+        }
+        this.scrollCallbackMap.clear();
+    }
+
+    protected disposeResizeListener: Function;
+    protected resizeObserver: ResizeObserver = undefined;
+
     protected initialize(): void {
     protected initialize(): void {
         this.fullOffsetLeft = 0;
         this.fullOffsetLeft = 0;
         this.fullOffsetTop = 0;
         this.fullOffsetTop = 0;
-        let nextParent: HTMLElement = this.osmdSheetMusicContainer;
-        while (nextParent) {
-            this.fullOffsetLeft += nextParent.offsetLeft;
-            this.fullOffsetTop += nextParent.offsetTop;
-            nextParent = nextParent.offsetParent as HTMLElement;
+        let nextOffsetParent: HTMLElement = this.osmdSheetMusicContainer;
+
+        const entries: HTMLElement[] = [];
+        const self: WebDisplayInteractionManager = this;
+        if(ResizeObserver){
+            this.resizeObserver = new ResizeObserver((observedElements: ResizeObserverEntry[]) => {
+                WebDisplayInteractionManager.resizeCallback(observedElements, self);
+            });
         }
         }
+        while (nextOffsetParent) {
+            this.fullOffsetLeft += nextOffsetParent.offsetLeft;
+            this.fullOffsetTop += nextOffsetParent.offsetTop;
+            if(!ResizeObserver){
+                entries.push(nextOffsetParent);
+            } else {
+                this.resizeObserver.observe(nextOffsetParent);
+            }
+            nextOffsetParent = nextOffsetParent.offsetParent as HTMLElement;
+        }
+
+        if(!ResizeObserver){
+            let resizeListener: (this: Window, ev: UIEvent) => any = (): void => {
+                WebDisplayInteractionManager.resizeCallback(entries, self);
+            };
+            //Resize observer not avail. on this browser, default to window event
+            window.addEventListener("resize", resizeListener);
+
+            this.disposeResizeListener = (): void => {
+                window.removeEventListener("resize", resizeListener);
+                resizeListener = undefined;
+            };
+        } else {
+            this.disposeResizeListener = (): void => {
+                self.resizeObserver.disconnect();
+                self.resizeObserver = undefined;
+            };
+        }
+        self.registerScrollOffsets();
     }
     }
 
 
     protected dispose(): void {
     protected dispose(): void {
-        this.osmdSheetMusicContainer.removeEventListener(this.downEventName, this.downEventListener);
-        this.osmdSheetMusicContainer.removeEventListener("touchend", this.touchEndEventListener.bind(this));
-        this.osmdSheetMusicContainer.removeEventListener(this.moveEventName, this.moveEventListener);
-        window.removeEventListener("resize", this.resizeEventListener.bind(this));
+        this.disposeResizeListener();
+        for(const eventName of this.EventCallbackMap.keys()){
+            const result: [HTMLElement|Document, EventListener] = this.EventCallbackMap.getValue(eventName);
+            result[0].removeEventListener(eventName, result[1]);
+        }
+        this.EventCallbackMap.clear();
+        this.deregisterScrollOffsets();
+        this.scrollCallbackMap.clear();
+        this.parentScrollMap.clear();
     }
     }
 
 
     //TODO: Much of this pulled from annotations code. Once we get the two branches together, combine common code
     //TODO: Much of this pulled from annotations code. Once we get the two branches together, combine common code
@@ -51,17 +172,19 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
     protected get moveEventName(): string {
     protected get moveEventName(): string {
         return this.isTouchDevice ? "touchmove" : "mousemove";
         return this.isTouchDevice ? "touchmove" : "mousemove";
     }
     }
+    protected EventCallbackMap: Dictionary<string, [HTMLElement|Document, EventListener]> =
+                new Dictionary<string, [HTMLElement|Document, EventListener]>();
 
 
     private listenForInteractions(): void {
     private listenForInteractions(): void {
-        const self: WebDisplayInteractionManager = this;
-        //this.osmdSheetMusicContainer[this.downEventName] = this.downEventListener.bind(this);
-        this.osmdSheetMusicContainer.addEventListener(this.downEventName, this.downEventListener.bind(this));
-
-        this.osmdSheetMusicContainer.addEventListener("touchend", this.touchEndEventListener.bind(this));
-
-        document.addEventListener(self.moveEventName, this.moveEventListener.bind(this));
-
-        window.addEventListener("resize", this.resizeEventListener.bind(this));
+        const downEvent: (clickEvent: MouseEvent | TouchEvent) => void = this.downEventListener.bind(this);
+        const endTouchEvent: (clickEvent: TouchEvent) => void = this.touchEndEventListener.bind(this);
+        const moveEvent: (clickEvent: MouseEvent | TouchEvent) => void = this.moveEventListener.bind(this);
+        this.osmdSheetMusicContainer.addEventListener(this.downEventName, downEvent);
+        this.osmdSheetMusicContainer.addEventListener("touchend", endTouchEvent);
+        document.addEventListener(this.moveEventName, moveEvent);
+        this.EventCallbackMap.setValue(this.downEventName, [this.osmdSheetMusicContainer, downEvent]);
+        this.EventCallbackMap.setValue("touchend", [this.osmdSheetMusicContainer, endTouchEvent]);
+        this.EventCallbackMap.setValue(this.moveEventName, [document, moveEvent]);
     }
     }
 
 
     //Millis of how long is valid for the next click of a double click
     //Millis of how long is valid for the next click of a double click
@@ -102,12 +225,18 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
     }
     }
 
 
     private moveEventListener(mouseMoveEvent: MouseEvent | TouchEvent): void {
     private moveEventListener(mouseMoveEvent: MouseEvent | TouchEvent): void {
-        //mouseMoveEvent.preventDefault();
+
         let x: number = 0;
         let x: number = 0;
         let y: number = 0;
         let y: number = 0;
         if (this.isTouchDevice && mouseMoveEvent instanceof TouchEvent) {
         if (this.isTouchDevice && mouseMoveEvent instanceof TouchEvent) {
-            x = mouseMoveEvent.touches[0].clientX;
-            y = mouseMoveEvent.touches[0].clientY;
+            let touch: Touch = undefined;
+            if(mouseMoveEvent.touches && mouseMoveEvent.touches.length > 0){
+                touch = mouseMoveEvent.touches[0];
+            } else if(mouseMoveEvent.changedTouches && mouseMoveEvent.changedTouches.length > 0){
+                touch = mouseMoveEvent.changedTouches[0];
+            }
+            x = touch?.clientX;
+            y = touch?.clientY;
         } else if (mouseMoveEvent instanceof MouseEvent) {
         } else if (mouseMoveEvent instanceof MouseEvent) {
             x = mouseMoveEvent.clientX;
             x = mouseMoveEvent.clientX;
             y = mouseMoveEvent.clientY;
             y = mouseMoveEvent.clientY;
@@ -117,7 +246,13 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
     }
     }
 
 
     private touchEndEventListener(clickEvent: TouchEvent): void {
     private touchEndEventListener(clickEvent: TouchEvent): void {
-        const touchMinusOffset: PointF2D = this.getOffsetCoordinates(clickEvent.touches[0].pageX, clickEvent.touches[0].pageY);
+        let touch: Touch = undefined;
+        if(clickEvent.touches && clickEvent.touches.length > 0){
+            touch = clickEvent.touches[0];
+        } else if(clickEvent.changedTouches && clickEvent.changedTouches.length > 0){
+            touch = clickEvent.changedTouches[0];
+        }
+        const touchMinusOffset: PointF2D = this.getOffsetCoordinates(touch?.pageX, touch?.pageY);
         this.touchUp(touchMinusOffset.x, touchMinusOffset.y);
         this.touchUp(touchMinusOffset.x, touchMinusOffset.y);
     }
     }
 
 
@@ -127,8 +262,8 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
     }
     }
 
 
     private getOffsetCoordinates(clickX: number, clickY: number): PointF2D {
     private getOffsetCoordinates(clickX: number, clickY: number): PointF2D {
-        const sheetX: number = clickX - this.fullOffsetLeft;
-        const sheetY: number = clickY - this.fullOffsetTop;
+        const sheetX: number = clickX - this.fullOffsetLeft + this.fullScrollLeft;
+        const sheetY: number = clickY - this.fullOffsetTop + this.fullScrollTop;
         return new PointF2D(sheetX, sheetY);
         return new PointF2D(sheetX, sheetY);
     }
     }
 }
 }