diff --git a/src/components/app/test/gesture-collision/main.html b/src/components/app/test/gesture-collision/main.html index 69a5f933ea..30d995cbf2 100644 --- a/src/components/app/test/gesture-collision/main.html +++ b/src/components/app/test/gesture-collision/main.html @@ -22,6 +22,16 @@ + + Apple + + + + + Apple + + + @@ -94,6 +104,16 @@ + + Apple + + + + + Apple + + + diff --git a/src/components/app/test/gesture-collision/page1.html b/src/components/app/test/gesture-collision/page1.html index 9d8061d33a..6c685e1f71 100644 --- a/src/components/app/test/gesture-collision/page1.html +++ b/src/components/app/test/gesture-collision/page1.html @@ -74,6 +74,16 @@ + + Apple + + + + + Apple + + + diff --git a/src/components/toggle/toggle-gesture.ts b/src/components/toggle/toggle-gesture.ts new file mode 100644 index 0000000000..c9cbe2236d --- /dev/null +++ b/src/components/toggle/toggle-gesture.ts @@ -0,0 +1,45 @@ +import { GestureController, GesturePriority, GESTURE_TOGGLE } from '../../gestures/gesture-controller'; +import { PanGesture } from '../../gestures/drag-gesture'; +import { pointerCoord } from '../../util/dom'; +import { NativeRafDebouncer } from '../../util/debouncer'; +import { Toggle } from './toggle'; + +/** + * @private + */ +export class ToggleGesture extends PanGesture { + + constructor(public toogle: Toggle, gestureCtrl: GestureController) { + super(toogle.getNativeElement(), { + maxAngle: 20, + threshold: 0, + debouncer: new NativeRafDebouncer(), + gesture: gestureCtrl.createGesture({ + name: GESTURE_TOGGLE, + priority: GesturePriority.Toggle, + }) + }); + } + + canStart(ev: any): boolean { + return true; + } + + onDragStart(ev: any) { + ev.preventDefault(); + + this.toogle._onDragStart(pointerCoord(ev).x); + } + + onDragMove(ev: any) { + ev.preventDefault(); + + this.toogle._onDragMove(pointerCoord(ev).x); + } + + onDragEnd(ev: any) { + ev.preventDefault(); + + this.toogle._onDragEnd(pointerCoord(ev).x); + } +} diff --git a/src/components/toggle/toggle.ts b/src/components/toggle/toggle.ts index e829f8b929..d84f977b16 100644 --- a/src/components/toggle/toggle.ts +++ b/src/components/toggle/toggle.ts @@ -3,13 +3,13 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Config } from '../../config/config'; import { Form, IonicTapInput } from '../../util/form'; -import { isTrueProperty } from '../../util/util'; +import { isTrueProperty, assert } from '../../util/util'; import { Ion } from '../ion'; import { Item } from '../item/item'; -import { pointerCoord } from '../../util/dom'; import { Key } from '../../util/key'; import { Haptic } from '../../util/haptic'; -import { UIEventManager } from '../../util/ui-event-manager'; +import { ToggleGesture } from './toggle-gesture'; +import { GestureController } from '../../gestures/gesture-controller'; export const TOGGLE_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, @@ -76,28 +76,18 @@ export const TOGGLE_VALUE_ACCESSOR: any = { encapsulation: ViewEncapsulation.None, }) export class Toggle extends Ion implements IonicTapInput, AfterContentInit, ControlValueAccessor, OnDestroy { - /** @private */ - _checked: boolean = false; - /** @private */ - _init: boolean = false; - /** @private */ - _disabled: boolean = false; - /** @private */ - _labelId: string; - /** @private */ - _activated: boolean = false; - /** @private */ - _startX: number; - /** @private */ - _msPrv: number = 0; - /** @private */ - _fn: Function = null; - /** @private */ - _events: UIEventManager = new UIEventManager(); - /** - * @private - */ + _checked: boolean = false; + _init: boolean = false; + _disabled: boolean = false; + _labelId: string; + _activated: boolean = false; + _startX: number; + _msPrv: number = 0; + _fn: Function = null; + _gesture: ToggleGesture; + + /** @private */ id: string; /** @@ -126,8 +116,9 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont config: Config, elementRef: ElementRef, renderer: Renderer, - public _haptic: Haptic, - @Optional() public _item: Item + private _haptic: Haptic, + @Optional() public _item: Item, + private _gestureCtrl: GestureController ) { super(config, elementRef, renderer, 'toggle'); _form.register(this); @@ -142,59 +133,63 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont /** * @private */ - pointerDown(ev: UIEvent): boolean { - this._startX = pointerCoord(ev).x; - this._activated = true; - return true; + ngAfterContentInit() { + this._init = true; + this._gesture = new ToggleGesture(this, this._gestureCtrl); + this._gesture.listen(); } /** * @private */ - pointerMove(ev: UIEvent) { - if (this._startX) { - let currentX = pointerCoord(ev).x; - console.debug('toggle, pointerMove', ev.type, currentX); + _onDragStart(startX: number) { + this._startX = startX; + this._activated = true; + } - if (this._checked) { - if (currentX + 15 < this._startX) { - this.onChange(false); - this._haptic.selection(); - this._startX = currentX; - this._activated = true; - } + /** + * @private + */ + _onDragMove(currentX: number) { + assert(this._startX, '_startX must be valid'); - } else if (currentX - 15 > this._startX) { - this.onChange(true); - // Create a haptic event + console.debug('toggle, pointerMove', currentX); + + if (this._checked) { + if (currentX + 15 < this._startX) { + this.onChange(false); this._haptic.selection(); this._startX = currentX; - this._activated = (currentX < this._startX + 5); + this._activated = true; } + + } else if (currentX - 15 > this._startX) { + this.onChange(true); + this._haptic.selection(); + this._startX = currentX; + this._activated = (currentX < this._startX + 5); } } /** * @private */ - pointerUp(ev: UIEvent) { - if (this._startX) { - let endX = pointerCoord(ev).x; + _onDragEnd(endX: number) { + assert(this._startX, '_startX must be valid'); - if (this.checked) { - if (this._startX + 4 > endX) { - this.onChange(false); - this._haptic.selection(); - } - - } else if (this._startX - 4 < endX) { - this.onChange(true); + if (this.checked) { + if (this._startX + 4 > endX) { + this.onChange(false); this._haptic.selection(); } - this._activated = false; - this._startX = null; + } else if (this._startX - 4 < endX) { + this.onChange(true); + this._haptic.selection(); } + + this._activated = false; + this._startX = null; } /** @@ -273,19 +268,6 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont */ onTouched() {} - /** - * @private - */ - ngAfterContentInit() { - this._init = true; - this._events.pointerEvents({ - elementRef: this._elementRef, - pointerDown: this.pointerDown.bind(this), - pointerMove: this.pointerMove.bind(this), - pointerUp: this.pointerUp.bind(this) - }); - } - /** * @private */ @@ -310,7 +292,7 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont */ ngOnDestroy() { this._form.deregister(this); - this._events.unlistenAll(); + this._gesture.destroy(); this._fn = null; } diff --git a/src/gestures/drag-gesture.ts b/src/gestures/drag-gesture.ts index d76101503e..25619e767f 100644 --- a/src/gestures/drag-gesture.ts +++ b/src/gestures/drag-gesture.ts @@ -23,6 +23,7 @@ export interface PanGestureConfig { * @private */ export class PanGesture { + private debouncer: Debouncer; private events: UIEventManager = new UIEventManager(false); private pointerEvents: PointerEvents; @@ -58,7 +59,9 @@ export class PanGesture { capture: opts.capture, passive: opts.passive }; - this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle); + if (opts.threshold > 0) { + this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle); + } } listen() { @@ -100,40 +103,42 @@ export class PanGesture { return false; } } - - let coord = pointerCoord(ev); - this.detector.start(coord); this.started = true; this.captured = false; + + const coord = pointerCoord(ev); + if (this.detector) { + this.detector.start(coord); + + } else { + if (!this.tryToCapture(ev)) { + this.started = false; + this.captured = false; + this.gestute.release(); + return false; + } + } return true; } pointerMove(ev: any) { - if (!this.started) { + assert(this.started === true, 'started must be true'); + if (this.captured) { + this.debouncer.debounce(() => { + this.onDragMove(ev); + }); return; } - this.debouncer.debounce(() => { - if (this.captured) { - this.onDragMove(ev); - return; - } - let coord = pointerCoord(ev); - if (this.detector.detect(coord)) { - if (this.detector.pan() !== 0 && - (!this.gestute || this.gestute.capture())) { - this.onDragStart(ev); - this.captured = true; - return; + assert(this.detector, 'detector has to be valid'); + const coord = pointerCoord(ev); + if (this.detector.detect(coord)) { + if (this.detector.pan() !== 0) { + if (!this.tryToCapture(ev)) { + this.abort(ev); } - - // Detection/capturing was not successful, aborting! - this.started = false; - this.captured = false; - this.pointerEvents.stop(); - this.notCaptured(ev); } - }); + } } pointerUp(ev: any) { @@ -151,6 +156,26 @@ export class PanGesture { this.started = false; } + tryToCapture(ev: any): boolean { + assert(this.started === true, 'started has be true'); + assert(this.captured === false, 'captured has be false'); + + if (this.gestute && !this.gestute.capture()) { + return false; + } + this.onDragStart(ev); + this.captured = true; + return true; + } + + abort(ev: any) { + this.started = false; + this.captured = false; + this.gestute.release(); + this.pointerEvents.stop(); + this.notCaptured(ev); + } + getNativeElement(): HTMLElement { return this.element; } diff --git a/src/gestures/gesture-controller.ts b/src/gestures/gesture-controller.ts index 1e5b3d749b..a5a8410150 100644 --- a/src/gestures/gesture-controller.ts +++ b/src/gestures/gesture-controller.ts @@ -14,6 +14,9 @@ export const GESTURE_ITEM_SWIPE = 'item-swipe'; /** @private */ export const GESTURE_REFRESHER = 'refresher'; +/** @private */ +export const GESTURE_TOGGLE = 'toggle'; + /** * @private */ @@ -24,11 +27,13 @@ export const enum GesturePriority { Normal = 0, High = 10, VeryHigh = 20, + VeryVeryHigh = 30, SlidingItem = Low, MenuSwipe = High, GoBackSwipe = VeryHigh, Refresher = Normal, + Toggle = VeryVeryHigh } /** diff --git a/src/gestures/recognizers.ts b/src/gestures/recognizers.ts index b2495c1e78..ecd2cfb92c 100644 --- a/src/gestures/recognizers.ts +++ b/src/gestures/recognizers.ts @@ -2,15 +2,17 @@ import { PointerCoordinates } from '../util/dom'; export class PanRecognizer { + private startCoord: PointerCoordinates; 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); + const radians = maxAngle * (Math.PI / 180); this.maxCosine = Math.cos(radians); this.threshold = threshold * threshold; } @@ -26,12 +28,13 @@ export class PanRecognizer { 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; + const deltaX = (coord.x - this.startCoord.x); + const deltaY = (coord.y - this.startCoord.y); + const distance = deltaX * deltaX + deltaY * deltaY; + if (distance >= this.threshold) { - let angle = Math.atan2(deltaY, deltaX); - let cosine = (this.direction === 'y') + var angle = Math.atan2(deltaY, deltaX); + var cosine = (this.direction === 'y') ? Math.sin(angle) : Math.cos(angle);