diff --git a/src/gestures/simulator.ts b/src/gestures/simulator.ts new file mode 100644 index 0000000000..dc304bcf20 --- /dev/null +++ b/src/gestures/simulator.ts @@ -0,0 +1,163 @@ +import { pointerCoord, Coordinates } from '../util/dom'; + +interface Point { + coord: Coordinates; + duration: number; +} + +export class Simulate { + private index: number = 0; + private points: Point[] = []; + public timedelta: number = 1 / 60; + + public static from(x: any, y?: number): Simulate { + let s = new Simulate(); + return s.start(x, y); + } + + reset(): Simulate { + this.index = 0; + return this; + } + + start(x: any, y?: number): Simulate { + this.points = []; + return this.to(x, y); + } + + to(x: any, y?: number): Simulate { + this.newPoint(parseCoordinates(x, y), 1); + return this; + } + + delta(x: any, y?: number): Simulate { + let newPoint = parseCoordinates(x, y); + let prevCoord = this.getLastPoint().coord; + newPoint.x += prevCoord.x; + newPoint.y += prevCoord.y; + + this.newPoint(newPoint, 1); + return this; + } + + deltaPolar(angle: number, distance: number): Simulate { + angle *= Math.PI / 180; + let prevCoord = this.getLastPoint().coord; + let coord = { + x: prevCoord.x + (Math.cos(angle) * distance), + y: prevCoord.y + (Math.sin(angle) * distance) + }; + this.newPoint(coord, 1); + return this; + } + + toPolar(angle: number, distance: number): Simulate { + angle *= Math.PI / 180; + let coord = { + x: Math.cos(angle) * distance, + y: Math.sin(angle) * distance + }; + this.newPoint(coord, 1); + return this; + } + + duration(duration: number): Simulate { + this.getLastPoint().duration = duration; + return this; + } + + velocity(vel: number): Simulate { + let p1 = this.getLastPoint(); + let p2 = this.getPreviousPoint(); + let d = distance(p1, p2); + return this.duration(d / vel); + } + + swipeRight(maxAngle: number, distance: number): Simulate { + // x------> + let angle = randomAngle(maxAngle); + return this.deltaPolar(angle, distance); + } + + swipeLeft(maxAngle: number, distance: number): Simulate { + // <------x + let angle = randomAngle(maxAngle) + 180; + return this.deltaPolar(angle, distance); + } + + swipeTop(maxAngle: number, distance: number): Simulate { + let angle = randomAngle(maxAngle) + 90; + return this.deltaPolar(angle, distance); + } + + swipeBottom(maxAngle: number, distance: number): Simulate { + let angle = randomAngle(maxAngle) - 90; + return this.deltaPolar(angle, distance); + } + + run(callback: Function) { + let points = this.points; + let len = points.length - 1; + let i = 0; + for (; i < len; i++) { + var p1 = points[i].coord; + var p2 = points[i + 1].coord; + var duration = points[i + 1].duration; + var vectorX = p2.x - p1.x; + var vectorY = p2.y - p1.y; + var nuSteps = Math.ceil(duration / this.timedelta); + vectorX /= nuSteps; + vectorY /= nuSteps; + for (let j = 0; j < nuSteps; j++) { + callback({ + x: p1.x + vectorX * j, + y: p1.y + vectorY * j + }); + } + } + this.index = i; + + return this; + } + + + private newPoint(coord: Coordinates, duration: number) { + this.points.push({ + coord: coord, + duration: duration, + }); + } + + private getLastPoint(): Point { + let len = this.points.length; + if (len > 0) { + return this.points[len - 1]; + } + throw new Error('can not call point'); + } + + private getPreviousPoint(): Point { + let len = this.points.length; + if (len > 1) { + return this.points[len - 2]; + } + throw new Error('can not call point'); + } +} + +function randomAngle(maxAngle: number): number { + return (Math.random() * maxAngle * 2) - maxAngle; +} + +function distance(a: Coordinates, b: Coordinates): number { + let deltaX = a.x - b.x; + let deltaY = a.y - a.y; + return Math.hypot(deltaX, deltaY); +} + +function parseCoordinates(coord: Coordinates | number, y?: number): Coordinates { + if (typeof coord === 'number') { + return { x: coord, y: y }; + } + return coord; +} \ No newline at end of file diff --git a/src/gestures/test/gesture-controller.spec.ts b/src/gestures/test/gesture-controller.spec.ts index 6456ea6364..b930d1a95b 100644 --- a/src/gestures/test/gesture-controller.spec.ts +++ b/src/gestures/test/gesture-controller.spec.ts @@ -3,13 +3,13 @@ import { GestureController, DisableScroll } from '../../../src'; export function run() { it('should create an instance of GestureController', () => { - let c = new GestureController(); + let c = new GestureController(null); expect(c.isCaptured()).toEqual(false); expect(c.isScrollDisabled()).toEqual(false); }); it('should test scrolling enable/disable stack', () => { - let c = new GestureController(); + let c = new GestureController(null); c.enableScroll(1); expect(c.isScrollDisabled()).toEqual(false); @@ -37,7 +37,7 @@ export function run() { }); it('should test gesture enable/disable stack', () => { - let c = new GestureController(); + let c = new GestureController(null); c.enableGesture('swipe', 1); expect(c.isDisabled('swipe')).toEqual(false); @@ -71,7 +71,7 @@ export function run() { it('should test if canStart', () => { - let c = new GestureController(); + let c = new GestureController(null); expect(c.canStart('event')).toEqual(true); expect(c.canStart('event1')).toEqual(true); expect(c.canStart('event')).toEqual(true); @@ -82,7 +82,7 @@ export function run() { it('should initialize a delegate without options', () => { - let c = new GestureController(); + let c = new GestureController(null); let g = c.create('event'); expect(g['name']).toEqual('event'); expect(g.priority).toEqual(0); @@ -97,7 +97,7 @@ export function run() { it('should initialize a delegate with options', () => { - let c = new GestureController(); + let c = new GestureController(null); let g = c.create('swipe', { priority: -123, disableScroll: DisableScroll.DuringCapture, @@ -112,7 +112,7 @@ export function run() { }); it('should test if several gestures can be started', () => { - let c = new GestureController(); + let c = new GestureController(null); let g1 = c.create('swipe'); let g2 = c.create('swipe1', {priority: 3}); let g3 = c.create('swipe2', {priority: 4}); @@ -147,7 +147,7 @@ export function run() { it('should test if several gestures try to capture at the same time', () => { - let c = new GestureController(); + let c = new GestureController(null); let g1 = c.create('swipe1'); let g2 = c.create('swipe2', { priority: 2 }); let g3 = c.create('swipe3', { priority: 3 }); @@ -199,7 +199,7 @@ export function run() { it('should destroy correctly', () => { - let c = new GestureController(); + let c = new GestureController(null); let g = c.create('swipe', { priority: 123, disableScroll: DisableScroll.Always, @@ -233,7 +233,7 @@ export function run() { it('should disable some events', () => { - let c = new GestureController(); + let c = new GestureController(null); let goback = c.create('goback'); expect(goback.canStart()).toEqual(true); @@ -276,7 +276,7 @@ export function run() { }); it('should disable scrolling on capture', () => { - let c = new GestureController(); + let c = new GestureController(null); let g = c.create('goback', { disableScroll: DisableScroll.DuringCapture, }); diff --git a/src/gestures/test/recognizers.spec.ts b/src/gestures/test/recognizers.spec.ts new file mode 100644 index 0000000000..cd3a47bcd6 --- /dev/null +++ b/src/gestures/test/recognizers.spec.ts @@ -0,0 +1,206 @@ + +import { PanRecognizer } from '../../../src/gestures/recognizers'; +import { Simulate } from '../../../src/gestures/simulator'; + +export function run() { + + it('should not fire if it did not start', () => { + let p = new PanRecognizer('x', 2, 2); + expect(p.pan()).toEqual(0); + + Simulate.from(0, 0).to(99, 0).run((coord: Coordinates) => { + expect(p.detect(coord)).toEqual(false); + }); + }); + + it('should reset', () => { + let p = new PanRecognizer('x', 2, 2); + + p.start({ x: 0, y: 0 }); + expect(p.pan()).toEqual(0); + + Simulate.from(0, 0).to(10, 0).run((coord: Coordinates) => { + p.detect(coord); + }); + expect(p.pan()).toEqual(1); + + p.start({ x: 0, y: 0 }); + expect(p.pan()).toEqual(0); + + Simulate.from(0, 0).to(-10, 0).run((coord: Coordinates) => { + p.detect(coord); + }); + expect(p.pan()).toEqual(-1); + }); + + it('should fire with large threshold', () => { + let detected = false; + let p = new PanRecognizer('x', 100, 40); + p.start({ x: 0, y: 0 }); + + Simulate + .from(0, 0).to(99, 0) + // Since threshold is 100, it should not fire yet + .run((coord: Coordinates) => expect(p.detect(coord)).toEqual(false)) + + // Now it should fire + .delta(2, 0) + .run((coord: Coordinates) => { + if (p.detect(coord)) { + // it should detect a horizontal pan + expect(p.pan()).toEqual(1); + detected = true; + } + }) + + // It should not detect again + .delta(20, 0) + .to(0, 0) + .to(102, 0) + .run((coord: Coordinates) => expect(p.detect(coord)).toEqual(false)); + + expect(detected).toEqual(true); + }); + + it('should detect swipe left', () => { + let p = new PanRecognizer('x', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(19, 21).delta(-30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(1); + + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(-19, 21).delta(-30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(1); + }); + + it('should detect swipe right', () => { + let p = new PanRecognizer('x', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(180 - 19, 21).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(-1); + + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(180 + 19, 21).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(-1); + }); + + it('should NOT detect swipe left', () => { + let p = new PanRecognizer('x', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(21, 21).delta(-30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(0); + + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(-21, 21).delta(-30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(0); + }); + + it('should NOT detect swipe right', () => { + let p = new PanRecognizer('x', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(180 - 21, 21).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(0); + + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(180 + 21, 21).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(0); + }); + + + + it('should detect swipe top', () => { + let p = new PanRecognizer('y', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(90 - 19, 21).delta(-30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(1); + + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(90 + 19, 21).delta(-30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(1); + }); + + it('should detect swipe bottom', () => { + let p = new PanRecognizer('y', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(-90 + 19, 21).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(-1); + + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(-90 - 19, 21).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(-1); + }); + + it('should NOT detect swipe top', () => { + let p = new PanRecognizer('y', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(90 - 21, 21).delta(-30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(0); + + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(90 + 21, 21).delta(-30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(0); + }); + + it('should NOT detect swipe bottom', () => { + let p = new PanRecognizer('y', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(-90 + 21, 21).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(0); + + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(-90 - 21, 21).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + expect(p.pan()).toEqual(0); + }); + + it('should NOT confuse between pan Y and X', () => { + let p = new PanRecognizer('x', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).deltaPolar(90, 21).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + + expect(p.pan()).toEqual(0); + }); + + it('should NOT confuse between pan X and Y', () => { + let p = new PanRecognizer('y', 20, 20); + p.start({ x: 0, y: 0 }); + Simulate + .from(0, 0).delta(30, 0) + .run((coord: Coordinates) => p.detect(coord)); + + expect(p.pan()).toEqual(0); + }); +}