|
@@ -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);
|
|
}
|
|
}
|
|
}
|
|
}
|