refactor(gestures): no longer use hammer for drag gestures

This commit is contained in:
Manu Mtz.-Almeida
2016-07-15 18:23:36 +02:00
committed by Adam Bradley
parent 11a24b98aa
commit 5909fa4ba5
12 changed files with 299 additions and 316 deletions

View File

@ -1,42 +1,142 @@
import { Gesture } from './gesture';
import { defaults } from '../util';
import { GestureDelegate } from '../gestures/gesture-controller';
import { PointerEvents, UIEventManager } from '../util/ui-event-manager';
import { PanRecognizer } from './recognizers';
import { pointerCoord, Coordinates } from '../util/dom';
/**
* @private
*/
export interface PanGestureConfig {
threshold?: number;
maxAngle?: number;
direction?: 'x' | 'y';
gesture?: GestureDelegate;
}
export class DragGesture extends Gesture {
public dragging: boolean;
/**
* @private
*/
export class PanGesture {
private dragging: boolean;
private events: UIEventManager = new UIEventManager(false);
private pointerEvents: PointerEvents;
private detector: PanRecognizer;
private started: boolean = false;
private captured: boolean = false;
public isListening: boolean = false;
protected gestute: GestureDelegate;
protected direction: string;
constructor(element: HTMLElement, opts = {}) {
defaults(opts, {});
super(element, opts);
constructor(private element: HTMLElement, opts: PanGestureConfig = {}) {
defaults(opts, {
threshold: 20,
maxAngle: 40,
direction: 'x'
});
this.gestute = opts.gesture;
this.direction = opts.direction;
this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle);
}
listen() {
super.listen();
this.on('panstart', (ev: UIEvent) => {
if (this.onDragStart(ev) !== false) {
this.dragging = true;
}
});
this.on('panmove', (ev: UIEvent) => {
if (!this.dragging) return;
if (this.onDrag(ev) === false) {
this.dragging = false;
}
});
this.on('panend', (ev: UIEvent) => {
if (!this.dragging) return;
this.onDragEnd(ev);
this.dragging = false;
});
if (!this.isListening) {
this.pointerEvents = this.events.pointerEvents({
element: this.element,
pointerDown: this.pointerDown.bind(this),
pointerMove: this.pointerMove.bind(this),
pointerUp: this.pointerUp.bind(this),
});
this.isListening = true;
}
}
onDrag(ev: any): boolean { return true; }
onDragStart(ev: any): boolean { return true; }
onDragEnd(ev: any): void {}
unlisten() {
this.gestute && this.gestute.release();
this.events.unlistenAll();
this.isListening = false;
}
destroy() {
this.gestute && this.gestute.destroy();
this.unlisten();
this.element = null;
}
pointerDown(ev: any): boolean {
if (this.started) {
return;
}
if (!this.canStart(ev)) {
return false;
}
if (this.gestute) {
// Release fallback
this.gestute.release();
// Start gesture
if (!this.gestute.start()) {
return false;
}
}
let coord = pointerCoord(ev);
this.detector.start(coord);
this.started = true;
this.captured = false;
return true;
}
pointerMove(ev: any) {
if (!this.started) {
return;
}
if (this.captured) {
this.onDragMove(ev);
return;
}
let coord = pointerCoord(ev);
if (this.detector.detect(coord)) {
if (this.detector.pan() !== 0 && this.canCapture(ev) &&
(!this.gestute || this.gestute.capture())) {
this.onDragStart(ev);
this.captured = true;
return;
}
// Detection/capturing was not successful, aborting!
this.started = false;
this.captured = false;
this.pointerEvents.stop();
this.notCaptured(ev);
}
}
pointerUp(ev: any) {
if (!this.started) {
return;
}
this.gestute && this.gestute.release();
if (this.captured) {
this.onDragEnd(ev);
} else {
this.notCaptured(ev);
}
this.captured = false;
this.started = false;
}
getNativeElement(): HTMLElement {
return this.element;
}
// Implemented in a subclass
canStart(ev: any): boolean { return true; }
canCapture(ev: any): boolean { return true; }
onDragStart(ev: any) { }
onDragMove(ev: any) { }
onDragEnd(ev: any) { }
notCaptured(ev: any) { }
}

View File

@ -4,11 +4,16 @@ import { App } from '../components/app/app';
export const enum GesturePriority {
Minimun = -10000,
NavigationOptional = -20,
Navigation = -10,
VeryLow = -20,
Low = -10,
Normal = 0,
Interactive = 10,
Input = 20,
High = 10,
VeryHigh = 20,
SlidingItem = Low,
MenuSwipe = High,
GoBackSwipe = VeryHigh,
Refresher = Normal,
}
export const enum DisableScroll {

View File

@ -0,0 +1,58 @@
import { pointerCoord, Coordinates } from '../util/dom';
export class PanRecognizer {
private startCoord: Coordinates;
private dirty: boolean = false;
private threshold: number;
private maxCosine: number;
private _angle: any = 0;
private _isPan: number = 0;
constructor(private direction: string, threshold: number, maxAngle: number) {
let radians = maxAngle * (Math.PI / 180);
this.maxCosine = Math.cos(radians);
this.threshold = threshold * threshold;
}
start(coord: Coordinates) {
this.startCoord = coord;
this._angle = 0;
this._isPan = 0;
this.dirty = true;
}
detect(coord: Coordinates): boolean {
if (!this.dirty) {
return false;
}
let deltaX = (coord.x - this.startCoord.x);
let deltaY = (coord.y - this.startCoord.y);
let distance = deltaX * deltaX + deltaY * deltaY;
if (distance >= this.threshold) {
let angle = Math.atan2(deltaY, deltaX);
let cosine = (this.direction === 'y')
? Math.sin(angle)
: Math.cos(angle);
this._angle = angle;
if (cosine > this.maxCosine) {
this._isPan = 1;
} else if (cosine < -this.maxCosine) {
this._isPan = -1;
} else {
this._isPan = 0;
}
this.dirty = false;
return true;
}
return false;
}
angle(): any {
return this._angle;
}
pan(): number {
return this._isPan;
}
}

View File

@ -1,6 +1,6 @@
import {SlideGesture} from './slide-gesture';
import {defaults} from '../util/util';
import {windowDimensions} from '../util/dom';
import { SlideGesture } from './slide-gesture';
import { defaults } from '../util/util';
import { pointerCoord, windowDimensions } from '../util/dom';
/**
* @private
@ -22,8 +22,9 @@ export class SlideEdgeGesture extends SlideGesture {
}
canStart(ev: any): boolean {
let coord = pointerCoord(ev);
this._d = this.getContainerDimensions();
return this.edges.every(edge => this._checkEdge(edge, ev.center));
return this.edges.every(edge => this._checkEdge(edge, coord));
}
getContainerDimensions() {

View File

@ -1,16 +1,15 @@
import {DragGesture} from './drag-gesture';
import {clamp} from '../util';
import { PanGesture } from './drag-gesture';
import { clamp } from '../util';
import { pointerCoord } from '../util/dom';
/**
* @private
*/
export class SlideGesture extends DragGesture {
export class SlideGesture extends PanGesture {
public slide: SlideData = null;
constructor(element: HTMLElement, opts = {}) {
super(element, opts);
this.element = element;
}
/*
@ -20,7 +19,7 @@ export class SlideGesture extends DragGesture {
getSlideBoundaries(slide: SlideData, ev: any) {
return {
min: 0,
max: this.element.offsetWidth
max: this.getNativeElement().offsetWidth
};
}
@ -33,48 +32,43 @@ export class SlideGesture extends DragGesture {
return 0;
}
canStart(ev: any): boolean {
return true;
}
onDragStart(ev: any): boolean {
if (!this.canStart(ev)) {
return false;
}
onDragStart(ev: any) {
this.slide = {};
this.onSlideBeforeStart(this.slide, ev);
var {min, max} = this.getSlideBoundaries(this.slide, ev);
let {min, max} = this.getSlideBoundaries(this.slide, ev);
let coord = <any>pointerCoord(ev);
this.slide.min = min;
this.slide.max = max;
this.slide.elementStartPos = this.getElementStartPos(this.slide, ev);
this.slide.pointerStartPos = ev.center[this.direction];
this.slide.pos = this.slide.pointerStartPos = coord[this.direction];
this.slide.timestamp = Date.now();
this.slide.started = true;
this.slide.velocity = 0;
this.onSlideStart(this.slide, ev);
return true;
}
onDrag(ev: any): boolean {
if (!this.slide || !this.slide.started) {
return false;
}
onDragMove(ev: any) {
let coord = <any>pointerCoord(ev);
let newPos = coord[this.direction];
let newTimestamp = Date.now();
let velocity = (newPos - this.slide.pos) / (newTimestamp - this.slide.timestamp);
this.slide.pos = ev.center[this.direction];
this.slide.pos = newPos;
this.slide.timestamp = newTimestamp;
this.slide.distance = clamp(
this.slide.min,
this.slide.pos - this.slide.pointerStartPos + this.slide.elementStartPos,
newPos - this.slide.pointerStartPos + this.slide.elementStartPos,
this.slide.max
);
this.slide.delta = this.slide.pos - this.slide.pointerStartPos;
this.slide.velocity = velocity;
this.slide.delta = newPos - this.slide.pointerStartPos;
this.onSlide(this.slide, ev);
return true;
}
onDragEnd(ev: any) {
if (!this.slide || !this.slide.started) return;
this.onSlideEnd(this.slide, ev);
this.slide = null;
}
@ -85,6 +79,9 @@ export class SlideGesture extends DragGesture {
onSlideEnd(slide?: SlideData, ev?: any): void {}
}
/**
* @private
*/
export interface SlideData {
min?: number;
max?: number;
@ -92,6 +89,8 @@ export interface SlideData {
delta?: number;
started?: boolean;
pos?: any;
timestamp?: number;
pointerStartPos?: number;
elementStartPos?: number;
velocity?: number;
}