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

@ -58,6 +58,22 @@
</ion-item-options> </ion-item-options>
</ion-item-sliding> </ion-item-sliding>
<ion-item-sliding>
<ion-item>LEFT button</ion-item>
<ion-item-options side="left">
<button>Test</button>
</ion-item-options>
</ion-item-sliding>
<ion-item-sliding>
<ion-item>RIGHT button</ion-item>
<ion-item-options>
<button>Test</button>
</ion-item-options>
</ion-item-sliding>
<button ion-item (click)="goToPage1()">Push same page</button> <button ion-item (click)="goToPage1()">Push same page</button>
<ion-item> <ion-item>

View File

@ -4,35 +4,29 @@ import { List } from '../list/list';
import { closest, Coordinates, pointerCoord } from '../../util/dom'; import { closest, Coordinates, pointerCoord } from '../../util/dom';
import { PointerEvents, UIEventManager } from '../../util/ui-event-manager'; import { PointerEvents, UIEventManager } from '../../util/ui-event-manager';
import { GestureDelegate, GestureOptions, GesturePriority } from '../../gestures/gesture-controller'; import { GestureDelegate, GestureOptions, GesturePriority } from '../../gestures/gesture-controller';
import { PanGesture } from '../../gestures/drag-gesture';
const DRAG_THRESHOLD = 10; const DRAG_THRESHOLD = 10;
const MAX_ATTACK_ANGLE = 20; const MAX_ATTACK_ANGLE = 20;
export class ItemSlidingGesture { export class ItemSlidingGesture extends PanGesture {
private preSelectedContainer: ItemSliding = null; private preSelectedContainer: ItemSliding = null;
private selectedContainer: ItemSliding = null; private selectedContainer: ItemSliding = null;
private openContainer: ItemSliding = null; private openContainer: ItemSliding = null;
private events: UIEventManager = new UIEventManager(false);
private panDetector: PanXRecognizer = new PanXRecognizer(DRAG_THRESHOLD, MAX_ATTACK_ANGLE);
private pointerEvents: PointerEvents;
private firstCoordX: number; private firstCoordX: number;
private firstTimestamp: number; private firstTimestamp: number;
private gesture: GestureDelegate;
constructor(public list: List) { constructor(public list: List) {
this.gesture = list.gestureCtrl.create('item-sliding', { super(list.getNativeElement(), {
priority: GesturePriority.Interactive, maxAngle: MAX_ATTACK_ANGLE,
}); threshold: DRAG_THRESHOLD,
gesture: list.gestureCtrl.create('item-sliding', {
this.pointerEvents = this.events.pointerEvents({ priority: GesturePriority.SlidingItem,
element: list.getNativeElement(), })
pointerDown: this.pointerStart.bind(this),
pointerMove: this.pointerMove.bind(this),
pointerUp: this.pointerEnd.bind(this),
}); });
} }
private pointerStart(ev: any): boolean { canStart(ev: any): boolean {
if (this.selectedContainer) { if (this.selectedContainer) {
return false; return false;
} }
@ -42,85 +36,50 @@ export class ItemSlidingGesture {
this.closeOpened(); this.closeOpened();
return false; return false;
} }
// Close open container if it is not the selected one. // Close open container if it is not the selected one.
if (container !== this.openContainer) { if (container !== this.openContainer) {
this.closeOpened(); this.closeOpened();
} }
// Try to start gesture
if (!this.gesture.start()) {
this.gesture.release();
return false;
}
let coord = pointerCoord(ev); let coord = pointerCoord(ev);
this.preSelectedContainer = container; this.preSelectedContainer = container;
this.panDetector.start(coord);
this.firstCoordX = coord.x; this.firstCoordX = coord.x;
this.firstTimestamp = Date.now(); this.firstTimestamp = Date.now();
return true; return true;
} }
private pointerMove(ev: any) { onDragStart(ev: any) {
if (this.selectedContainer) { ev.preventDefault();
this.onDragMove(ev);
return;
}
let coord = pointerCoord(ev); let coord = pointerCoord(ev);
if (this.panDetector.detect(coord)) {
if (this.panDetector.isPanX() && this.gesture.capture()) {
this.onDragStart(ev, coord);
return;
}
// Detection/capturing was not successful, aborting!
this.closeOpened();
this.pointerEvents.stop();
}
}
private pointerEnd(ev: any) {
this.gesture.release();
if (this.selectedContainer) {
this.onDragEnd(ev);
} else {
this.closeOpened();
}
}
private onDragStart(ev: any, coord: Coordinates): boolean {
let container = getContainer(ev);
if (!container) {
console.debug('onDragStart, no itemContainerEle');
return false;
}
ev.preventDefault();
this.selectedContainer = this.openContainer = this.preSelectedContainer; this.selectedContainer = this.openContainer = this.preSelectedContainer;
container.startSliding(coord.x); this.selectedContainer.startSliding(coord.x);
} }
private onDragMove(ev: any) { onDragMove(ev: any) {
let coordX = pointerCoord(ev).x;
ev.preventDefault(); ev.preventDefault();
let coordX = pointerCoord(ev).x;
this.selectedContainer.moveSliding(coordX); this.selectedContainer.moveSliding(coordX);
} }
private onDragEnd(ev: any) { onDragEnd(ev: any) {
ev.preventDefault(); ev.preventDefault();
let coordX = pointerCoord(ev).x; let coordX = pointerCoord(ev).x;
let deltaX = (coordX - this.firstCoordX); let deltaX = (coordX - this.firstCoordX);
let deltaT = (Date.now() - this.firstTimestamp); let deltaT = (Date.now() - this.firstTimestamp);
let openAmount = this.selectedContainer.endSliding(deltaX / deltaT); let openAmount = this.selectedContainer.endSliding(deltaX / deltaT);
this.selectedContainer = null; this.selectedContainer = null;
this.preSelectedContainer = null; this.preSelectedContainer = null;
} }
notCaptured(ev: any) {
this.closeOpened();
}
closeOpened(): boolean { closeOpened(): boolean {
this.selectedContainer = null; this.selectedContainer = null;
this.gesture.release();
if (this.openContainer) { if (this.openContainer) {
this.openContainer.close(); this.openContainer.close();
@ -131,8 +90,7 @@ export class ItemSlidingGesture {
} }
destroy() { destroy() {
this.gesture.destroy(); super.destroy();
this.events.unlistenAll();
this.closeOpened(); this.closeOpened();
this.list = null; this.list = null;
@ -148,70 +106,4 @@ function getContainer(ev: any): ItemSliding {
return (<any>ele)['$ionComponent']; return (<any>ele)['$ionComponent'];
} }
return null; return null;
} }
class AngleRecognizer {
private startCoord: Coordinates;
private sumCoord: Coordinates;
private dirty: boolean;
private _angle: any = null;
private threshold: number;
constructor(threshold: number) {
this.threshold = threshold ** 2;
}
start(coord: Coordinates) {
this.startCoord = coord;
this._angle = 0;
this.dirty = true;
}
angle(): any {
return this._angle;
}
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) {
this._angle = Math.atan2(deltaY, deltaX);
this.dirty = false;
return true;
}
return false;
}
}
class PanXRecognizer extends AngleRecognizer {
private _isPanX: boolean;
private maxAngle: number;
constructor(threshold: number, maxAngle: number) {
super(threshold);
this.maxAngle = maxAngle * (Math.PI / 180);
}
start(coord: Coordinates) {
super.start(coord);
this._isPanX = false;
}
isPanX(): boolean {
return this._isPanX;
}
detect(coord: Coordinates): boolean {
if (super.detect(coord)) {
let angle = Math.abs(this.angle());
this._isPanX = (angle < this.maxAngle || Math.abs(angle - Math.PI) < this.maxAngle);
return true;
}
return false;
}
}

View File

@ -92,6 +92,7 @@ export class List extends Ion {
} else if (!this._slidingGesture) { } else if (!this._slidingGesture) {
console.debug('enableSlidingItems'); console.debug('enableSlidingItems');
this._slidingGesture = new ItemSlidingGesture(this); this._slidingGesture = new ItemSlidingGesture(this);
this._slidingGesture.listen();
} }
} }

View File

@ -2,69 +2,35 @@ import { Menu } from './menu';
import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture'; import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
import { SlideData } from '../../gestures/slide-gesture'; import { SlideData } from '../../gestures/slide-gesture';
import { assign } from '../../util/util'; import { assign } from '../../util/util';
import { GestureDelegate, GesturePriority } from '../../gestures/gesture-controller'; import { GesturePriority } from '../../gestures/gesture-controller';
const DEGREES_TO_RADIANS = Math.PI / 180;
const MIN_COSINE = Math.cos(40 * DEGREES_TO_RADIANS);
/** /**
* Gesture attached to the content which the menu is assigned to * Gesture attached to the content which the menu is assigned to
*/ */
export class MenuContentGesture extends SlideEdgeGesture { export class MenuContentGesture extends SlideEdgeGesture {
gesture: GestureDelegate;
constructor(public menu: Menu, contentEle: HTMLElement, options: any = {}) { constructor(public menu: Menu, contentEle: HTMLElement, options: any = {}) {
super(contentEle, assign({ super(contentEle, assign({
direction: 'x', direction: 'x',
edge: menu.side, edge: menu.side,
threshold: 0, threshold: 0,
maxEdgeStart: menu.maxEdgeStart || 75 maxEdgeStart: menu.maxEdgeStart || 50,
maxAngle: 40,
gesture: menu.gestureCtrl.create('menu-swipe', {
priority: GesturePriority.MenuSwipe,
})
}, options)); }, options));
this.gesture = menu.gestureCtrl.create('menu-swipe', {
priority: GesturePriority.NavigationOptional,
});
} }
canStart(ev: any): boolean { canStart(ev: any): boolean {
if (this.shouldStart(ev)) {
return this.gesture.capture();
}
this.gesture.release();
return false;
}
shouldStart(ev: any): boolean {
let menu = this.menu; let menu = this.menu;
if (!menu.enabled || !menu.swipeEnabled) { return (
console.debug('menu can not start, isEnabled:', menu.enabled, 'isSwipeEnabled:', menu.swipeEnabled, 'side:', menu.side); menu.enabled &&
return false; menu.swipeEnabled &&
} (menu.isOpen || super.canStart(ev))
);
if (ev.distance > 50) {
// the distance is longer than you'd expect a side menu swipe to be
console.debug('menu can not start, distance too far:', ev.distance, 'side:', menu.side);
return false;
}
console.debug('menu shouldCapture,', menu.side, 'isOpen', menu.isOpen, 'angle', ev.angle, 'distance', ev.distance);
if (menu.isOpen) {
return true;
}
let cosine = Math.cos(ev.angle * DEGREES_TO_RADIANS);
if (menu.side === 'right') {
if (cosine < -MIN_COSINE) {
return super.canStart(ev);
}
} else {
if (cosine > MIN_COSINE) {
return super.canStart(ev);
}
}
return false;
} }
// Set CSS, then wait one frame for it to apply before sliding starts // Set CSS, then wait one frame for it to apply before sliding starts
onSlideBeforeStart(slide: SlideData, ev: any) { onSlideBeforeStart(slide: SlideData, ev: any) {
console.debug('menu gesture, onSlideBeforeStart', this.menu.side); console.debug('menu gesture, onSlideBeforeStart', this.menu.side);
@ -75,29 +41,26 @@ export class MenuContentGesture extends SlideEdgeGesture {
let z = (this.menu.side === 'right' ? slide.min : slide.max); let z = (this.menu.side === 'right' ? slide.min : slide.max);
let stepValue = (slide.distance / z); let stepValue = (slide.distance / z);
console.debug('menu gesture, onSlide', this.menu.side, 'distance', slide.distance, 'min', slide.min, 'max', slide.max, 'z', z, 'stepValue', stepValue); console.debug('menu gesture, onSlide', this.menu.side, 'distance', slide.distance, 'min', slide.min, 'max', slide.max, 'z', z, 'stepValue', stepValue);
ev.srcEvent.preventDefault();
ev.preventDefault(); ev.preventDefault();
this.menu.swipeProgress(stepValue); this.menu.swipeProgress(stepValue);
} }
onSlideEnd(slide: SlideData, ev: any) { onSlideEnd(slide: SlideData, ev: any) {
this.gesture.release();
let z = (this.menu.side === 'right' ? slide.min : slide.max); let z = (this.menu.side === 'right' ? slide.min : slide.max);
let currentStepValue = (slide.distance / z); let currentStepValue = (slide.distance / z);
let velocity = slide.velocity;
z = Math.abs(z * 0.5); z = Math.abs(z * 0.5);
let shouldCompleteRight = (ev.velocityX >= 0) let shouldCompleteRight = (velocity >= 0)
&& (ev.velocityX > 0.2 || slide.delta > z); && (velocity > 0.2 || slide.delta > z);
let shouldCompleteLeft = (ev.velocityX <= 0) let shouldCompleteLeft = (velocity <= 0)
&& (ev.velocityX < -0.2 || slide.delta < -z); && (velocity < -0.2 || slide.delta < -z);
console.debug( console.debug(
'menu gesture, onSlide', this.menu.side, 'menu gesture, onSlide', this.menu.side,
'distance', slide.distance, 'distance', slide.distance,
'delta', slide.delta, 'delta', slide.delta,
'velocityX', ev.velocityX, 'velocity', velocity,
'min', slide.min, 'min', slide.min,
'max', slide.max, 'max', slide.max,
'shouldCompleteLeft', shouldCompleteLeft, 'shouldCompleteLeft', shouldCompleteLeft,
@ -131,27 +94,5 @@ export class MenuContentGesture extends SlideEdgeGesture {
max: this.menu.width() max: this.menu.width()
}; };
} }
unlisten() {
this.gesture.release();
super.unlisten();
}
destroy() {
this.gesture.destroy();
super.destroy();
}
} }
/**
* Gesture attached to the actual menu itself
*/
export class MenuTargetGesture extends MenuContentGesture {
constructor(menu: Menu, menuEle: HTMLElement) {
super(menu, menuEle, {
maxEdgeStart: 0
});
this.gesture.priority++;
}
}

View File

@ -5,7 +5,7 @@ import { Config } from '../../config/config';
import { Ion } from '../ion'; import { Ion } from '../ion';
import { isTrueProperty } from '../../util/util'; import { isTrueProperty } from '../../util/util';
import { Keyboard } from '../../util/keyboard'; import { Keyboard } from '../../util/keyboard';
import { MenuContentGesture, MenuTargetGesture } from './menu-gestures'; import { MenuContentGesture } from './menu-gestures';
import { MenuController } from './menu-controller'; import { MenuController } from './menu-controller';
import { MenuType } from './menu-types'; import { MenuType } from './menu-types';
import { Platform } from '../../platform/platform'; import { Platform } from '../../platform/platform';
@ -191,8 +191,7 @@ import { GestureController } from '../../gestures/gesture-controller';
export class Menu extends Ion { export class Menu extends Ion {
private _preventTime: number = 0; private _preventTime: number = 0;
private _cntEle: HTMLElement; private _cntEle: HTMLElement;
private _cntGesture: MenuTargetGesture; private _cntGesture: MenuContentGesture;
private _menuGesture: MenuContentGesture;
private _type: MenuType; private _type: MenuType;
private _resizeUnreg: Function; private _resizeUnreg: Function;
private _isEnabled: boolean = true; private _isEnabled: boolean = true;
@ -337,8 +336,7 @@ export class Menu extends Ion {
self._renderer.setElementAttribute(self._elementRef.nativeElement, 'type', self.type); self._renderer.setElementAttribute(self._elementRef.nativeElement, 'type', self.type);
// add the gestures // add the gestures
self._cntGesture = new MenuContentGesture(self, self.getContentElement()); self._cntGesture = new MenuContentGesture(self, document.body);
self._menuGesture = new MenuTargetGesture(self, self.getNativeElement());
// register listeners if this menu is enabled // register listeners if this menu is enabled
// check if more than one menu is on the same side // check if more than one menu is on the same side
@ -389,16 +387,12 @@ export class Menu extends Ion {
if (self._isEnabled && self._isSwipeEnabled && !self._cntGesture.isListening) { if (self._isEnabled && self._isSwipeEnabled && !self._cntGesture.isListening) {
// should listen, but is not currently listening // should listen, but is not currently listening
console.debug('menu, gesture listen', self.side); console.debug('menu, gesture listen', self.side);
self._zone.runOutsideAngular(function() { self._cntGesture.listen();
self._cntGesture.listen();
self._menuGesture.listen();
});
} else if (self._cntGesture.isListening && (!self._isEnabled || !self._isSwipeEnabled)) { } else if (self._cntGesture.isListening && (!self._isEnabled || !self._isSwipeEnabled)) {
// should not listen, but is currently listening // should not listen, but is currently listening
console.debug('menu, gesture unlisten', self.side); console.debug('menu, gesture unlisten', self.side);
self._cntGesture.unlisten(); self._cntGesture.unlisten();
self._menuGesture.unlisten();
} }
} }
} }
@ -625,7 +619,6 @@ export class Menu extends Ion {
ngOnDestroy() { ngOnDestroy() {
this._menuCtrl.unregister(this); this._menuCtrl.unregister(this);
this._cntGesture && this._cntGesture.destroy(); this._cntGesture && this._cntGesture.destroy();
this._menuGesture && this._menuGesture.destroy();
this._type && this._type.destroy(); this._type && this._type.destroy();
this._resizeUnreg && this._resizeUnreg(); this._resizeUnreg && this._resizeUnreg();
this._cntEle = null; this._cntEle = null;

View File

@ -8,8 +8,6 @@ import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
export class SwipeBackGesture extends SlideEdgeGesture { export class SwipeBackGesture extends SlideEdgeGesture {
private gesture: GestureDelegate;
constructor( constructor(
element: HTMLElement, element: HTMLElement,
options: any, options: any,
@ -18,32 +16,26 @@ export class SwipeBackGesture extends SlideEdgeGesture {
) { ) {
super(element, assign({ super(element, assign({
direction: 'x', direction: 'x',
maxEdgeStart: 75 maxEdgeStart: 75,
gesture: gestureCtlr.create('goback-swipe', {
priority: GesturePriority.GoBackSwipe,
})
}, options)); }, options));
this.gesture = gestureCtlr.create('goback-swipe', {
priority: GesturePriority.Navigation,
});
} }
canStart(ev: any): boolean { canStart(ev: any): boolean {
this.gesture.release();
// the gesture swipe angle must be mainly horizontal and the // the gesture swipe angle must be mainly horizontal and the
// gesture distance would be relatively short for a swipe back // gesture distance would be relatively short for a swipe back
// and swipe back must be possible on this nav controller // and swipe back must be possible on this nav controller
return ( return (
ev.angle > -40 &&
ev.angle < 40 &&
ev.distance < 50 &&
this._nav.canSwipeBack() && this._nav.canSwipeBack() &&
super.canStart(ev) && super.canStart(ev)
this.gesture.capture()
); );
} }
onSlideBeforeStart(slideData: SlideData, ev: any) { onSlideBeforeStart(slideData: SlideData, ev: any) {
console.debug('swipeBack, onSlideBeforeStart', ev.srcEvent.type); console.debug('swipeBack, onSlideBeforeStart', ev.type);
this._nav.swipeBackStart(); this._nav.swipeBackStart();
} }
@ -54,25 +46,10 @@ export class SwipeBackGesture extends SlideEdgeGesture {
} }
onSlideEnd(slide: SlideData, ev: any) { onSlideEnd(slide: SlideData, ev: any) {
let shouldComplete = (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5); let shouldComplete = (Math.abs(slide.velocity) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
let currentStepValue = (slide.distance / slide.max); let currentStepValue = (slide.distance / slide.max);
console.debug('swipeBack, onSlideEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue); console.debug('swipeBack, onSlideEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);
this._nav.swipeBackEnd(shouldComplete, currentStepValue); this._nav.swipeBackEnd(shouldComplete, currentStepValue);
this.gesture.release();
} }
unlisten() {
this.gesture.release();
super.unlisten();
}
destroy() {
this.gesture.destroy();
super.destroy();
}
} }

View File

@ -201,7 +201,7 @@ export class Refresher {
constructor(@Host() private _content: Content, private _zone: NgZone, gestureCtrl: GestureController) { constructor(@Host() private _content: Content, private _zone: NgZone, gestureCtrl: GestureController) {
_content.addCssClass('has-refresher'); _content.addCssClass('has-refresher');
this._gesture = gestureCtrl.create('refresher', { this._gesture = gestureCtrl.create('refresher', {
priority: GesturePriority.Interactive, priority: GesturePriority.Refresher,
}); });
} }

View File

@ -1,42 +1,142 @@
import { Gesture } from './gesture';
import { defaults } from '../util'; 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 * @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 = {}) { constructor(private element: HTMLElement, opts: PanGestureConfig = {}) {
defaults(opts, {}); defaults(opts, {
super(element, 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() { listen() {
super.listen(); if (!this.isListening) {
this.pointerEvents = this.events.pointerEvents({
this.on('panstart', (ev: UIEvent) => { element: this.element,
if (this.onDragStart(ev) !== false) { pointerDown: this.pointerDown.bind(this),
this.dragging = true; pointerMove: this.pointerMove.bind(this),
} pointerUp: this.pointerUp.bind(this),
}); });
this.isListening = 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;
});
} }
onDrag(ev: any): boolean { return true; } unlisten() {
onDragStart(ev: any): boolean { return true; } this.gestute && this.gestute.release();
onDragEnd(ev: any): void {} 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 { export const enum GesturePriority {
Minimun = -10000, Minimun = -10000,
NavigationOptional = -20, VeryLow = -20,
Navigation = -10, Low = -10,
Normal = 0, Normal = 0,
Interactive = 10, High = 10,
Input = 20, VeryHigh = 20,
SlidingItem = Low,
MenuSwipe = High,
GoBackSwipe = VeryHigh,
Refresher = Normal,
} }
export const enum DisableScroll { 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 { SlideGesture } from './slide-gesture';
import {defaults} from '../util/util'; import { defaults } from '../util/util';
import {windowDimensions} from '../util/dom'; import { pointerCoord, windowDimensions } from '../util/dom';
/** /**
* @private * @private
@ -22,8 +22,9 @@ export class SlideEdgeGesture extends SlideGesture {
} }
canStart(ev: any): boolean { canStart(ev: any): boolean {
let coord = pointerCoord(ev);
this._d = this.getContainerDimensions(); this._d = this.getContainerDimensions();
return this.edges.every(edge => this._checkEdge(edge, ev.center)); return this.edges.every(edge => this._checkEdge(edge, coord));
} }
getContainerDimensions() { getContainerDimensions() {

View File

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