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-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>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) { }
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
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 { 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() {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user