mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 05:58:26 +08:00
refactor(gestures): no longer use hammer for drag gestures
This commit is contained in:

committed by
Adam Bradley

parent
11a24b98aa
commit
5909fa4ba5
@ -58,6 +58,22 @@
|
||||
</ion-item-options>
|
||||
</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>
|
||||
|
||||
<ion-item>
|
||||
|
@ -4,35 +4,29 @@ import { List } from '../list/list';
|
||||
import { closest, Coordinates, pointerCoord } from '../../util/dom';
|
||||
import { PointerEvents, UIEventManager } from '../../util/ui-event-manager';
|
||||
import { GestureDelegate, GestureOptions, GesturePriority } from '../../gestures/gesture-controller';
|
||||
import { PanGesture } from '../../gestures/drag-gesture';
|
||||
|
||||
const DRAG_THRESHOLD = 10;
|
||||
const MAX_ATTACK_ANGLE = 20;
|
||||
|
||||
export class ItemSlidingGesture {
|
||||
export class ItemSlidingGesture extends PanGesture {
|
||||
private preSelectedContainer: ItemSliding = null;
|
||||
private selectedContainer: 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 firstTimestamp: number;
|
||||
private gesture: GestureDelegate;
|
||||
|
||||
constructor(public list: List) {
|
||||
this.gesture = list.gestureCtrl.create('item-sliding', {
|
||||
priority: GesturePriority.Interactive,
|
||||
});
|
||||
|
||||
this.pointerEvents = this.events.pointerEvents({
|
||||
element: list.getNativeElement(),
|
||||
pointerDown: this.pointerStart.bind(this),
|
||||
pointerMove: this.pointerMove.bind(this),
|
||||
pointerUp: this.pointerEnd.bind(this),
|
||||
super(list.getNativeElement(), {
|
||||
maxAngle: MAX_ATTACK_ANGLE,
|
||||
threshold: DRAG_THRESHOLD,
|
||||
gesture: list.gestureCtrl.create('item-sliding', {
|
||||
priority: GesturePriority.SlidingItem,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private pointerStart(ev: any): boolean {
|
||||
canStart(ev: any): boolean {
|
||||
if (this.selectedContainer) {
|
||||
return false;
|
||||
}
|
||||
@ -42,85 +36,50 @@ export class ItemSlidingGesture {
|
||||
this.closeOpened();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Close open container if it is not the selected one.
|
||||
if (container !== this.openContainer) {
|
||||
this.closeOpened();
|
||||
}
|
||||
|
||||
// Try to start gesture
|
||||
if (!this.gesture.start()) {
|
||||
this.gesture.release();
|
||||
return false;
|
||||
}
|
||||
|
||||
let coord = pointerCoord(ev);
|
||||
this.preSelectedContainer = container;
|
||||
this.panDetector.start(coord);
|
||||
this.firstCoordX = coord.x;
|
||||
this.firstTimestamp = Date.now();
|
||||
return true;
|
||||
}
|
||||
|
||||
private pointerMove(ev: any) {
|
||||
if (this.selectedContainer) {
|
||||
this.onDragMove(ev);
|
||||
return;
|
||||
}
|
||||
onDragStart(ev: any) {
|
||||
ev.preventDefault();
|
||||
|
||||
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;
|
||||
container.startSliding(coord.x);
|
||||
this.selectedContainer.startSliding(coord.x);
|
||||
}
|
||||
|
||||
private onDragMove(ev: any) {
|
||||
let coordX = pointerCoord(ev).x;
|
||||
onDragMove(ev: any) {
|
||||
ev.preventDefault();
|
||||
|
||||
let coordX = pointerCoord(ev).x;
|
||||
this.selectedContainer.moveSliding(coordX);
|
||||
}
|
||||
|
||||
private onDragEnd(ev: any) {
|
||||
onDragEnd(ev: any) {
|
||||
ev.preventDefault();
|
||||
|
||||
let coordX = pointerCoord(ev).x;
|
||||
let deltaX = (coordX - this.firstCoordX);
|
||||
let deltaT = (Date.now() - this.firstTimestamp);
|
||||
|
||||
let openAmount = this.selectedContainer.endSliding(deltaX / deltaT);
|
||||
this.selectedContainer = null;
|
||||
this.preSelectedContainer = null;
|
||||
}
|
||||
|
||||
notCaptured(ev: any) {
|
||||
this.closeOpened();
|
||||
}
|
||||
|
||||
closeOpened(): boolean {
|
||||
this.selectedContainer = null;
|
||||
this.gesture.release();
|
||||
|
||||
if (this.openContainer) {
|
||||
this.openContainer.close();
|
||||
@ -131,8 +90,7 @@ export class ItemSlidingGesture {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.gesture.destroy();
|
||||
this.events.unlistenAll();
|
||||
super.destroy();
|
||||
this.closeOpened();
|
||||
|
||||
this.list = null;
|
||||
@ -149,69 +107,3 @@ function getContainer(ev: any): ItemSliding {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ export class List extends Ion {
|
||||
} else if (!this._slidingGesture) {
|
||||
console.debug('enableSlidingItems');
|
||||
this._slidingGesture = new ItemSlidingGesture(this);
|
||||
this._slidingGesture.listen();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,69 +2,35 @@ import { Menu } from './menu';
|
||||
import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
|
||||
import { SlideData } from '../../gestures/slide-gesture';
|
||||
import { assign } from '../../util/util';
|
||||
import { GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
|
||||
|
||||
const DEGREES_TO_RADIANS = Math.PI / 180;
|
||||
const MIN_COSINE = Math.cos(40 * DEGREES_TO_RADIANS);
|
||||
import { GesturePriority } from '../../gestures/gesture-controller';
|
||||
|
||||
/**
|
||||
* Gesture attached to the content which the menu is assigned to
|
||||
*/
|
||||
export class MenuContentGesture extends SlideEdgeGesture {
|
||||
gesture: GestureDelegate;
|
||||
|
||||
constructor(public menu: Menu, contentEle: HTMLElement, options: any = {}) {
|
||||
super(contentEle, assign({
|
||||
direction: 'x',
|
||||
edge: menu.side,
|
||||
threshold: 0,
|
||||
maxEdgeStart: menu.maxEdgeStart || 75
|
||||
maxEdgeStart: menu.maxEdgeStart || 50,
|
||||
maxAngle: 40,
|
||||
gesture: menu.gestureCtrl.create('menu-swipe', {
|
||||
priority: GesturePriority.MenuSwipe,
|
||||
})
|
||||
}, options));
|
||||
|
||||
this.gesture = menu.gestureCtrl.create('menu-swipe', {
|
||||
priority: GesturePriority.NavigationOptional,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
if (!menu.enabled || !menu.swipeEnabled) {
|
||||
console.debug('menu can not start, isEnabled:', menu.enabled, 'isSwipeEnabled:', menu.swipeEnabled, 'side:', menu.side);
|
||||
return false;
|
||||
return (
|
||||
menu.enabled &&
|
||||
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
|
||||
onSlideBeforeStart(slide: SlideData, ev: any) {
|
||||
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 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);
|
||||
ev.srcEvent.preventDefault();
|
||||
ev.preventDefault();
|
||||
this.menu.swipeProgress(stepValue);
|
||||
}
|
||||
|
||||
onSlideEnd(slide: SlideData, ev: any) {
|
||||
this.gesture.release();
|
||||
|
||||
let z = (this.menu.side === 'right' ? slide.min : slide.max);
|
||||
let currentStepValue = (slide.distance / z);
|
||||
|
||||
let velocity = slide.velocity;
|
||||
z = Math.abs(z * 0.5);
|
||||
let shouldCompleteRight = (ev.velocityX >= 0)
|
||||
&& (ev.velocityX > 0.2 || slide.delta > z);
|
||||
let shouldCompleteRight = (velocity >= 0)
|
||||
&& (velocity > 0.2 || slide.delta > z);
|
||||
|
||||
let shouldCompleteLeft = (ev.velocityX <= 0)
|
||||
&& (ev.velocityX < -0.2 || slide.delta < -z);
|
||||
let shouldCompleteLeft = (velocity <= 0)
|
||||
&& (velocity < -0.2 || slide.delta < -z);
|
||||
|
||||
console.debug(
|
||||
'menu gesture, onSlide', this.menu.side,
|
||||
'distance', slide.distance,
|
||||
'delta', slide.delta,
|
||||
'velocityX', ev.velocityX,
|
||||
'velocity', velocity,
|
||||
'min', slide.min,
|
||||
'max', slide.max,
|
||||
'shouldCompleteLeft', shouldCompleteLeft,
|
||||
@ -131,27 +94,5 @@ export class MenuContentGesture extends SlideEdgeGesture {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { Config } from '../../config/config';
|
||||
import { Ion } from '../ion';
|
||||
import { isTrueProperty } from '../../util/util';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { MenuContentGesture, MenuTargetGesture } from './menu-gestures';
|
||||
import { MenuContentGesture } from './menu-gestures';
|
||||
import { MenuController } from './menu-controller';
|
||||
import { MenuType } from './menu-types';
|
||||
import { Platform } from '../../platform/platform';
|
||||
@ -191,8 +191,7 @@ import { GestureController } from '../../gestures/gesture-controller';
|
||||
export class Menu extends Ion {
|
||||
private _preventTime: number = 0;
|
||||
private _cntEle: HTMLElement;
|
||||
private _cntGesture: MenuTargetGesture;
|
||||
private _menuGesture: MenuContentGesture;
|
||||
private _cntGesture: MenuContentGesture;
|
||||
private _type: MenuType;
|
||||
private _resizeUnreg: Function;
|
||||
private _isEnabled: boolean = true;
|
||||
@ -337,8 +336,7 @@ export class Menu extends Ion {
|
||||
self._renderer.setElementAttribute(self._elementRef.nativeElement, 'type', self.type);
|
||||
|
||||
// add the gestures
|
||||
self._cntGesture = new MenuContentGesture(self, self.getContentElement());
|
||||
self._menuGesture = new MenuTargetGesture(self, self.getNativeElement());
|
||||
self._cntGesture = new MenuContentGesture(self, document.body);
|
||||
|
||||
// register listeners if this menu is enabled
|
||||
// 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) {
|
||||
// should listen, but is not currently listening
|
||||
console.debug('menu, gesture listen', self.side);
|
||||
self._zone.runOutsideAngular(function() {
|
||||
self._cntGesture.listen();
|
||||
self._menuGesture.listen();
|
||||
});
|
||||
|
||||
} else if (self._cntGesture.isListening && (!self._isEnabled || !self._isSwipeEnabled)) {
|
||||
// should not listen, but is currently listening
|
||||
console.debug('menu, gesture unlisten', self.side);
|
||||
self._cntGesture.unlisten();
|
||||
self._menuGesture.unlisten();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -625,7 +619,6 @@ export class Menu extends Ion {
|
||||
ngOnDestroy() {
|
||||
this._menuCtrl.unregister(this);
|
||||
this._cntGesture && this._cntGesture.destroy();
|
||||
this._menuGesture && this._menuGesture.destroy();
|
||||
this._type && this._type.destroy();
|
||||
this._resizeUnreg && this._resizeUnreg();
|
||||
this._cntEle = null;
|
||||
|
@ -8,8 +8,6 @@ import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
|
||||
|
||||
export class SwipeBackGesture extends SlideEdgeGesture {
|
||||
|
||||
private gesture: GestureDelegate;
|
||||
|
||||
constructor(
|
||||
element: HTMLElement,
|
||||
options: any,
|
||||
@ -18,32 +16,26 @@ export class SwipeBackGesture extends SlideEdgeGesture {
|
||||
) {
|
||||
super(element, assign({
|
||||
direction: 'x',
|
||||
maxEdgeStart: 75
|
||||
maxEdgeStart: 75,
|
||||
gesture: gestureCtlr.create('goback-swipe', {
|
||||
priority: GesturePriority.GoBackSwipe,
|
||||
})
|
||||
}, options));
|
||||
|
||||
this.gesture = gestureCtlr.create('goback-swipe', {
|
||||
priority: GesturePriority.Navigation,
|
||||
});
|
||||
}
|
||||
|
||||
canStart(ev: any): boolean {
|
||||
this.gesture.release();
|
||||
|
||||
// the gesture swipe angle must be mainly horizontal and the
|
||||
// gesture distance would be relatively short for a swipe back
|
||||
// and swipe back must be possible on this nav controller
|
||||
return (
|
||||
ev.angle > -40 &&
|
||||
ev.angle < 40 &&
|
||||
ev.distance < 50 &&
|
||||
this._nav.canSwipeBack() &&
|
||||
super.canStart(ev) &&
|
||||
this.gesture.capture()
|
||||
super.canStart(ev)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
onSlideBeforeStart(slideData: SlideData, ev: any) {
|
||||
console.debug('swipeBack, onSlideBeforeStart', ev.srcEvent.type);
|
||||
console.debug('swipeBack, onSlideBeforeStart', ev.type);
|
||||
this._nav.swipeBackStart();
|
||||
}
|
||||
|
||||
@ -54,25 +46,10 @@ export class SwipeBackGesture extends SlideEdgeGesture {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
console.debug('swipeBack, onSlideEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);
|
||||
|
||||
this._nav.swipeBackEnd(shouldComplete, currentStepValue);
|
||||
|
||||
this.gesture.release();
|
||||
}
|
||||
|
||||
unlisten() {
|
||||
this.gesture.release();
|
||||
super.unlisten();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.gesture.destroy();
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ export class Refresher {
|
||||
constructor(@Host() private _content: Content, private _zone: NgZone, gestureCtrl: GestureController) {
|
||||
_content.addCssClass('has-refresher');
|
||||
this._gesture = gestureCtrl.create('refresher', {
|
||||
priority: GesturePriority.Interactive,
|
||||
priority: GesturePriority.Refresher,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
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.on('panmove', (ev: UIEvent) => {
|
||||
if (!this.dragging) return;
|
||||
if (this.onDrag(ev) === false) {
|
||||
this.dragging = false;
|
||||
this.isListening = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.on('panend', (ev: UIEvent) => {
|
||||
if (!this.dragging) return;
|
||||
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);
|
||||
this.dragging = false;
|
||||
});
|
||||
} else {
|
||||
this.notCaptured(ev);
|
||||
}
|
||||
this.captured = false;
|
||||
this.started = false;
|
||||
}
|
||||
|
||||
onDrag(ev: any): boolean { return true; }
|
||||
onDragStart(ev: any): boolean { return true; }
|
||||
onDragEnd(ev: any): void {}
|
||||
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) { }
|
||||
}
|
||||
|
@ -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 {
|
||||
|
58
src/gestures/recognizers.ts
Normal file
58
src/gestures/recognizers.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user