fix(datetime): fix gesture

This commit is contained in:
Manu Mtz.-Almeida
2018-08-02 23:15:19 +02:00
parent b69d01de26
commit e9fd184175
9 changed files with 198 additions and 303 deletions

View File

@ -58,7 +58,7 @@ export class ItemSliding {
this.updateOptions(); this.updateOptions();
this.gesture = (await import('../../utils/gesture/gesture')).create({ this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
el: this.el, el: this.el,
queue: this.queue, queue: this.queue,
gestureName: 'item-swipe', gestureName: 'item-swipe',
@ -69,7 +69,7 @@ export class ItemSliding {
onMove: this.onDragMove.bind(this), onMove: this.onDragMove.bind(this),
onEnd: this.onDragEnd.bind(this), onEnd: this.onDragEnd.bind(this),
}); });
this.gesture.disabled = false; this.gesture.setDisabled(false);
} }
componentDidUnload() { componentDidUnload() {

View File

@ -175,7 +175,7 @@ export class Menu {
this.menuCtrl!._register(this); this.menuCtrl!._register(this);
this.ionMenuChange.emit({ disabled: !isEnabled, open: this._isOpen }); this.ionMenuChange.emit({ disabled: !isEnabled, open: this._isOpen });
this.gesture = (await import('../../utils/gesture/gesture')).create({ this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
el: this.doc, el: this.doc,
queue: this.queue, queue: this.queue,
gestureName: 'menu-swipe', gestureName: 'menu-swipe',
@ -439,7 +439,7 @@ export class Menu {
private updateState() { private updateState() {
const isActive = this.isActive(); const isActive = this.isActive();
if (this.gesture) { if (this.gesture) {
this.gesture.disabled = !isActive || !this.swipeEnabled; this.gesture.setDisabled(!isActive || !this.swipeEnabled);
} }
// Close menu inmediately // Close menu inmediately

View File

@ -41,7 +41,7 @@ export class Nav implements NavOutlet {
@Watch('swipeBackEnabled') @Watch('swipeBackEnabled')
swipeBackEnabledChanged() { swipeBackEnabledChanged() {
if (this.gesture) { if (this.gesture) {
this.gesture.disabled = !this.swipeBackEnabled; this.gesture.setDisabled(!this.swipeBackEnabled);
} }
} }
@ -110,7 +110,7 @@ export class Nav implements NavOutlet {
async componentDidLoad() { async componentDidLoad() {
this.rootChanged(); this.rootChanged();
this.gesture = (await import('../../utils/gesture/gesture')).create({ this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
el: this.win.document.body, el: this.win.document.body,
queue: this.queue, queue: this.queue,
gestureName: 'goback-swipe', gestureName: 'goback-swipe',

View File

@ -1,6 +1,6 @@
import { Component, Element, Prop, QueueApi } from '@stencil/core'; import { Component, Element, Prop, QueueApi } from '@stencil/core';
import { Gesture, GestureDetail, Mode, PickerColumn, PickerColumnOption } from '../../interface'; import { Gesture, GestureDetail, Mode, PickerColumn } from '../../interface';
import { hapticSelectionChanged } from '../../utils'; import { hapticSelectionChanged } from '../../utils';
import { clamp } from '../../utils/helpers'; import { clamp } from '../../utils/helpers';
import { createThemedClasses } from '../../utils/theme'; import { createThemedClasses } from '../../utils/theme';
@ -18,13 +18,12 @@ export class PickerColumnCmp {
private minY!: number; private minY!: number;
private maxY!: number; private maxY!: number;
private optHeight = 0; private optHeight = 0;
private pos: number[] = [];
private rotateFactor = 0; private rotateFactor = 0;
private scaleFactor = 1; private scaleFactor = 1;
private startY?: number;
private velocity = 0; private velocity = 0;
private y = 0; private y = 0;
private gesture?: Gesture; private gesture?: Gesture;
private rafId: any;
@Element() el!: HTMLElement; @Element() el!: HTMLElement;
@ -54,27 +53,17 @@ export class PickerColumnCmp {
this.refresh(); this.refresh();
this.gesture = (await import('../../utils/gesture/gesture')).create({ this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
el: this.el, el: this.el,
queue: this.queue, queue: this.queue,
gestureName: 'picker-swipe', gestureName: 'picker-swipe',
gesturePriority: 10, gesturePriority: 10,
threshold: 0, threshold: 0,
canStart: this.canStart.bind(this),
onStart: this.onDragStart.bind(this), onStart: this.onDragStart.bind(this),
onMove: this.onDragMove.bind(this), onMove: this.onDragMove.bind(this),
onEnd: this.onDragEnd.bind(this), onEnd: this.onDragEnd.bind(this),
}); });
this.gesture.disabled = false; this.gesture.setDisabled(false);
}
private optClick(ev: Event, index: number) {
if (!this.velocity) {
ev.preventDefault();
ev.stopPropagation();
this.setSelected(index, 150);
}
} }
private setSelected(selectedIndex: number, duration: number) { private setSelected(selectedIndex: number, duration: number) {
@ -85,6 +74,7 @@ export class PickerColumnCmp {
this.velocity = 0; this.velocity = 0;
// set what y position we're at // set what y position we're at
cancelAnimationFrame(this.rafId);
this.update(y, duration, true, true); this.update(y, duration, true, true);
} }
@ -92,16 +82,8 @@ export class PickerColumnCmp {
// ensure we've got a good round number :) // ensure we've got a good round number :)
y = Math.round(y); y = Math.round(y);
let i: number;
let button: any;
let opt: PickerColumnOption;
let optOffset: number;
let visible: boolean;
let translateY = 0; let translateY = 0;
let translateZ = 0; let translateZ = 0;
let rotateX: number;
let transform: string;
let selected: boolean;
const parent = this.el.querySelector('.picker-opts')!; const parent = this.el.querySelector('.picker-opts')!;
const children = parent.children; const children = parent.children;
@ -111,15 +93,15 @@ export class PickerColumnCmp {
const durationStr = (duration === 0) ? null : duration + 'ms'; const durationStr = (duration === 0) ? null : duration + 'ms';
const scaleStr = `scale(${this.scaleFactor})`; const scaleStr = `scale(${this.scaleFactor})`;
for (i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
button = children[i]; const button = children[i] as HTMLElement;
opt = this.col.options[i]; const opt = this.col.options[i];
optOffset = (i * this.optHeight) + y; const optOffset = (i * this.optHeight) + y;
visible = true; let visible = true;
transform = ''; let transform = '';
if (this.rotateFactor !== 0) { if (this.rotateFactor !== 0) {
rotateX = optOffset * this.rotateFactor; const rotateX = optOffset * this.rotateFactor;
if (Math.abs(rotateX) > 90) { if (Math.abs(rotateX) > 90) {
visible = false; visible = false;
} else { } else {
@ -135,7 +117,7 @@ export class PickerColumnCmp {
} }
} }
selected = selectedIndex === i; const selected = selectedIndex === i;
if (visible) { if (visible) {
transform += `translate3d(0px,${translateY}px,${translateZ}px) `; transform += `translate3d(0px,${translateY}px,${translateZ}px) `;
if (this.scaleFactor !== 1 && !selected) { if (this.scaleFactor !== 1 && !selected) {
@ -204,7 +186,7 @@ export class PickerColumnCmp {
? Math.max(this.velocity, 1) ? Math.max(this.velocity, 1)
: Math.min(this.velocity, -1); : Math.min(this.velocity, -1);
y = Math.round(this.y - this.velocity); y = Math.round(this.y + this.velocity);
if (y > this.minY) { if (y > this.minY) {
// whoops, it's trying to scroll up farther than the options we have! // whoops, it's trying to scroll up farther than the options we have!
@ -223,7 +205,7 @@ export class PickerColumnCmp {
if (notLockedIn) { if (notLockedIn) {
// isn't locked in yet, keep decelerating until it is // isn't locked in yet, keep decelerating until it is
this.queue.read(() => this.decelerate()); this.rafId = requestAnimationFrame(() => this.decelerate());
} }
} else if (this.y % this.optHeight !== 0) { } else if (this.y % this.optHeight !== 0) {
@ -246,11 +228,8 @@ export class PickerColumnCmp {
} }
// TODO should this check disabled? // TODO should this check disabled?
private canStart() {
return true;
}
private onDragStart(detail: GestureDetail): boolean { private onDragStart(detail: GestureDetail) {
// We have to prevent default in order to block scrolling under the picker // We have to prevent default in order to block scrolling under the picker
// but we DO NOT have to stop propagation, since we still want // but we DO NOT have to stop propagation, since we still want
// some "click" events to capture // some "click" events to capture
@ -259,14 +238,8 @@ export class PickerColumnCmp {
detail.event.stopPropagation(); detail.event.stopPropagation();
} }
// remember where the pointer started from
this.startY = detail.startY;
// reset everything // reset everything
this.velocity = 0; cancelAnimationFrame(this.rafId);
this.pos.length = 0;
this.pos.push(this.startY, Date.now());
const options = this.col.options; const options = this.col.options;
let minY = (options.length - 1); let minY = (options.length - 1);
let maxY = 0; let maxY = 0;
@ -279,7 +252,6 @@ export class PickerColumnCmp {
this.minY = (minY * this.optHeight * -1); this.minY = (minY * this.optHeight * -1);
this.maxY = (maxY * this.optHeight * -1); this.maxY = (maxY * this.optHeight * -1);
return true;
} }
private onDragMove(detail: GestureDetail) { private onDragMove(detail: GestureDetail) {
@ -288,15 +260,8 @@ export class PickerColumnCmp {
detail.event.stopPropagation(); detail.event.stopPropagation();
} }
const currentY = detail.currentY;
this.pos.push(currentY, Date.now());
if (this.startY === undefined) {
return;
}
// update the scroll position relative to pointer start position // update the scroll position relative to pointer start position
let y = this.y + (currentY - this.startY); let y = this.y + detail.deltaY;
if (y > this.minY) { if (y > this.minY) {
// scrolling up higher than scroll area // scrolling up higher than scroll area
@ -321,12 +286,6 @@ export class PickerColumnCmp {
} }
private onDragEnd(detail: GestureDetail) { private onDragEnd(detail: GestureDetail) {
if (this.startY === undefined) {
return;
}
this.velocity = 0;
if (this.bounceFrom > 0) { if (this.bounceFrom > 0) {
// bounce back up // bounce back up
this.update(this.minY, 100, true, true); this.update(this.minY, 100, true, true);
@ -337,36 +296,20 @@ export class PickerColumnCmp {
return; return;
} }
const endY = detail.currentY; this.velocity = clamp(-MAX_PICKER_SPEED, detail.velocityY * 17, MAX_PICKER_SPEED);
if (this.velocity === 0 && detail.deltaY === 0) {
const opt = (detail.event.target as Element).closest('.picker-opt');
if (opt && opt.hasAttribute('opt-index')) {
this.setSelected(parseInt(opt.getAttribute('opt-index')!, 10), 150);
}
this.pos.push(endY, Date.now()); } else {
if (Math.abs(detail.deltaY) > 3) {
const endPos = (this.pos.length - 1); const y = this.y + detail.deltaY;
let startPos = endPos; this.update(y, 0, true, true);
const timeRange = (Date.now() - 100); }
this.decelerate();
// move pointer to position measured 100ms ago
for (let i = endPos; i > 0 && this.pos[i] > timeRange; i -= 2) {
startPos = i;
} }
if (startPos !== endPos) {
// compute relative movement between these two points
const timeOffset = (this.pos[endPos] - this.pos[startPos]);
const movedTop = (this.pos[startPos - 1] - this.pos[endPos - 1]);
// based on XXms compute the movement to apply for each render step
const velocity = ((movedTop / timeOffset) * FRAME_MS);
this.velocity = clamp(-MAX_PICKER_SPEED, velocity, MAX_PICKER_SPEED);
}
if (Math.abs(endY - this.startY) > 3) {
const y = this.y + (endY - this.startY);
this.update(y, 0, true, true);
}
this.startY = undefined;
this.decelerate();
} }
private refresh() { private refresh() {
@ -406,45 +349,35 @@ export class PickerColumnCmp {
const col = this.col; const col = this.col;
const options = col.options.map(o => { const options = col.options.map(o => {
if (typeof o === 'string') { return (typeof o === 'string')
o = { text: o }; ? { text: o }
} : o;
return o;
}) })
.filter(o => o !== null); .filter(o => o !== null);
const results: JSX.Element[] = []; const Button = 'button' as any;
return [
if (col.prefix) { col.prefix && (
results.push(
<div class="picker-prefix" style={{ width: col.prefixWidth! }}> <div class="picker-prefix" style={{ width: col.prefixWidth! }}>
{col.prefix} {col.prefix}
</div> </div>
); ),
}
results.push(
<div class="picker-opts" style={{ maxWidth: col.optionsWidth! }}> <div class="picker-opts" style={{ maxWidth: col.optionsWidth! }}>
{options.map((o, index) => {options.map((o, index) =>
<button <Button
class={{ 'picker-opt': true, 'picker-opt-disabled': !!o.disabled }} class={{ 'picker-opt': true, 'picker-opt-disabled': !!o.disabled }}
disable-activated disable-activated
onClick={event => this.optClick(event, index)}> opt-index={index}>
{o.text} {o.text}
</button> </Button>
)} )}
</div> </div>,
); col.suffix && (
if (col.suffix) {
results.push(
<div class="picker-suffix" style={{ width: col.suffixWidth! }}> <div class="picker-suffix" style={{ width: col.suffixWidth! }}>
{col.suffix} {col.suffix}
</div> </div>
); )
} ];
return results;
} }
} }

View File

@ -98,7 +98,7 @@ export class Range implements BaseInput {
@Watch('disabled') @Watch('disabled')
protected disabledChanged() { protected disabledChanged() {
if (this.gesture) { if (this.gesture) {
this.gesture.disabled = this.disabled; this.gesture.setDisabled(this.disabled);
} }
this.emitStyle(); this.emitStyle();
} }
@ -145,7 +145,7 @@ export class Range implements BaseInput {
} }
async componentDidLoad() { async componentDidLoad() {
this.gesture = (await import('../../utils/gesture/gesture')).create({ this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
el: this.rangeSlider!, el: this.rangeSlider!,
queue: this.queue, queue: this.queue,
gestureName: 'range', gestureName: 'range',
@ -155,7 +155,7 @@ export class Range implements BaseInput {
onMove: this.onDragMove.bind(this), onMove: this.onDragMove.bind(this),
onEnd: this.onDragEnd.bind(this), onEnd: this.onDragEnd.bind(this),
}); });
this.gesture.disabled = this.disabled; this.gesture.setDisabled(this.disabled);
} }
@Listen('ionIncrease') @Listen('ionIncrease')

View File

@ -67,7 +67,7 @@ export class Refresher {
@Watch('disabled') @Watch('disabled')
disabledChanged() { disabledChanged() {
if (this.gesture) { if (this.gesture) {
this.gesture.disabled = this.disabled; this.gesture.setDisabled(this.disabled);
} }
} }
@ -102,7 +102,7 @@ export class Refresher {
console.error('ion-refresher did not attach, make sure the parent is an ion-content.'); console.error('ion-refresher did not attach, make sure the parent is an ion-content.');
} }
this.gesture = (await import('../../utils/gesture/gesture')).create({ this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
el: this.el.closest('ion-content') as any, el: this.el.closest('ion-content') as any,
queue: this.queue, queue: this.queue,
gestureName: 'refresher', gestureName: 'refresher',

View File

@ -40,7 +40,7 @@ export class ReorderGroup {
@Watch('disabled') @Watch('disabled')
disabledChanged() { disabledChanged() {
if (this.gesture) { if (this.gesture) {
this.gesture.disabled = this.disabled; this.gesture.setDisabled(this.disabled);
} }
} }
@ -51,7 +51,7 @@ export class ReorderGroup {
this.scrollEl = contentEl.getScrollElement(); this.scrollEl = contentEl.getScrollElement();
} }
this.gesture = (await import('../../utils/gesture/gesture')).create({ this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
el: this.doc.body, el: this.doc.body,
queue: this.queue, queue: this.queue,
gestureName: 'reorder', gestureName: 'reorder',

View File

@ -94,7 +94,7 @@ export class Toggle implements CheckboxInput {
'interactive-disabled': this.disabled, 'interactive-disabled': this.disabled,
}); });
if (this.gesture) { if (this.gesture) {
this.gesture.disabled = this.disabled; this.gesture.setDisabled(this.disabled);
} }
} }
@ -112,7 +112,7 @@ export class Toggle implements CheckboxInput {
} }
} }
this.gesture = (await import('../../utils/gesture/gesture')).create({ this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
el: this.el, el: this.el,
queue: this.queue, queue: this.queue,
gestureName: 'toggle', gestureName: 'toggle',

View File

@ -2,7 +2,7 @@ import { QueueApi } from '@stencil/core';
import { PanRecognizer } from './recognizers'; import { PanRecognizer } from './recognizers';
import { GestureDelegate, gestureController } from './gesture-controller'; import { gestureController } from './gesture-controller';
import { PointerEvents } from './pointer-events'; import { PointerEvents } from './pointer-events';
export interface GestureDetail { export interface GestureDetail {
@ -23,6 +23,11 @@ export interface GestureDetail {
export type GestureCallback = (detail?: GestureDetail) => boolean | void; export type GestureCallback = (detail?: GestureDetail) => boolean | void;
export interface Gesture {
setDisabled(disabled: boolean): void;
destroy(): void;
}
export interface GestureConfig { export interface GestureConfig {
el: Node; el: Node;
disableScroll?: boolean; disableScroll?: boolean;
@ -43,100 +48,70 @@ export interface GestureConfig {
notCaptured?: GestureCallback; notCaptured?: GestureCallback;
} }
export function create(config: GestureConfig) { export function createGesture(config: GestureConfig): Gesture {
return new Gesture(config); const finalConfig = {
} disableScroll: false,
direction: 'x',
gesturePriority: 0,
passive: true,
maxAngle: 40,
threshold: 10,
export class Gesture { ...config
};
private detail: GestureDetail; let hasCapturedPan = false;
private positions: number[] = []; let hasStartedPan = false;
private gesture: GestureDelegate; let hasFiredStart = true;
private pan: PanRecognizer; let isMoveQueued = false;
private hasCapturedPan = false;
private hasStartedPan = false;
private hasFiredStart = true;
private isMoveQueued = false;
private pointerEvents: PointerEvents;
private canStart?: GestureCallback; const canStart = finalConfig.canStart;
private onWillStart?: (_: GestureDetail) => Promise<void>; const onWillStart = finalConfig.onWillStart;
private onStart?: GestureCallback; const onStart = finalConfig.onStart;
private onMove?: GestureCallback; const onEnd = finalConfig.onEnd;
private onEnd?: GestureCallback; const notCaptured = finalConfig.notCaptured;
private notCaptured?: GestureCallback; const onMove = finalConfig.onMove;
private threshold: number; const threshold = finalConfig.threshold;
private queue: QueueApi; const queue = finalConfig.queue;
constructor(config: GestureConfig) { const detail = {
const finalConfig = { type: 'pan',
disableScroll: false, startX: 0,
direction: 'x', startY: 0,
gesturePriority: 0, startTimeStamp: 0,
passive: true, currentX: 0,
maxAngle: 40, currentY: 0,
threshold: 10, velocityX: 0,
velocityY: 0,
deltaX: 0,
deltaY: 0,
timeStamp: 0,
event: undefined as any,
data: undefined
};
...config const pointerEvents = new PointerEvents(
}; finalConfig.el,
pointerDown,
pointerMove,
pointerUp,
{
capture: false,
}
);
this.canStart = finalConfig.canStart; const pan = new PanRecognizer(finalConfig.direction, finalConfig.threshold, finalConfig.maxAngle);
this.onWillStart = finalConfig.onWillStart; const gesture = gestureController.createGesture({
this.onStart = finalConfig.onStart; name: config.gestureName,
this.onEnd = finalConfig.onEnd; priority: config.gesturePriority,
this.onMove = finalConfig.onMove; disableScroll: config.disableScroll
this.threshold = finalConfig.threshold; });
this.queue = finalConfig.queue;
this.detail = { function pointerDown(ev: UIEvent): boolean {
type: 'pan',
startX: 0,
startY: 0,
startTimeStamp: 0,
currentX: 0,
currentY: 0,
velocityX: 0,
velocityY: 0,
deltaX: 0,
deltaY: 0,
timeStamp: 0,
event: undefined as any,
data: undefined
};
this.pointerEvents = new PointerEvents(
finalConfig.el,
this.pointerDown.bind(this),
this.pointerMove.bind(this),
this.pointerUp.bind(this),
{
capture: false,
}
);
this.pan = new PanRecognizer(finalConfig.direction, finalConfig.threshold, finalConfig.maxAngle);
this.gesture = gestureController.createGesture({
name: config.gestureName,
priority: config.gesturePriority,
disableScroll: config.disableScroll
});
}
set disabled(disabled: boolean) {
this.pointerEvents.disabled = disabled;
}
destroy() {
this.gesture.destroy();
this.pointerEvents.destroy();
}
private pointerDown(ev: UIEvent): boolean {
const timeStamp = now(ev); const timeStamp = now(ev);
if (this.hasStartedPan || !this.hasFiredStart) { if (hasStartedPan || !hasFiredStart) {
return false; return false;
} }
const detail = this.detail;
updateDetail(ev, detail); updateDetail(ev, detail);
detail.startX = detail.currentX; detail.startX = detail.currentX;
@ -144,109 +119,90 @@ export class Gesture {
detail.startTimeStamp = detail.timeStamp = timeStamp; detail.startTimeStamp = detail.timeStamp = timeStamp;
detail.velocityX = detail.velocityY = detail.deltaX = detail.deltaY = 0; detail.velocityX = detail.velocityY = detail.deltaX = detail.deltaY = 0;
detail.event = ev; detail.event = ev;
this.positions.length = 0;
// Check if gesture can start // Check if gesture can start
if (this.canStart && this.canStart(detail) === false) { if (canStart && canStart(detail) === false) {
return false; return false;
} }
// Release fallback // Release fallback
this.gesture.release(); gesture.release();
// Start gesture // Start gesture
if (!this.gesture.start()) { if (!gesture.start()) {
return false; return false;
} }
this.positions.push(detail.currentX, detail.currentY, timeStamp); hasStartedPan = true;
this.hasStartedPan = true; if (threshold === 0) {
if (this.threshold === 0) { return tryToCapturePan();
return this.tryToCapturePan();
} }
this.pan.start(detail.startX, detail.startY); pan.start(detail.startX, detail.startY);
return true; return true;
} }
private pointerMove(ev: UIEvent) { function pointerMove(ev: UIEvent) {
// fast path, if gesture is currently captured // fast path, if gesture is currently captured
// do minimun job to get user-land even dispatched // do minimun job to get user-land even dispatched
if (this.hasCapturedPan) { if (hasCapturedPan) {
if (!this.isMoveQueued && this.hasFiredStart) { if (!isMoveQueued && hasFiredStart) {
this.isMoveQueued = true; isMoveQueued = true;
this.calcGestureData(ev); calcGestureData(ev);
this.queue.write(this.fireOnMove.bind(this)); queue.write(fireOnMove);
} }
return; return;
} }
// gesture is currently being detected // gesture is currently being detected
const detail = this.detail; calcGestureData(ev);
this.calcGestureData(ev); if (pan.detect(detail.currentX, detail.currentY)) {
if (this.pan.detect(detail.currentX, detail.currentY)) { if (pan.isGesture()) {
if (this.pan.isGesture()) { if (!tryToCapturePan()) {
if (!this.tryToCapturePan()) { abortGesture();
this.abortGesture();
} }
} }
} }
} }
private fireOnMove() { function fireOnMove() {
// Since fireOnMove is called inside a RAF, onEnd() might be called, // Since fireOnMove is called inside a RAF, onEnd() might be called,
// we must double check hasCapturedPan // we must double check hasCapturedPan
if (!this.hasCapturedPan) { if (!hasCapturedPan) {
return; return;
} }
const detail = this.detail; isMoveQueued = false;
this.isMoveQueued = false; if (onMove) {
if (this.onMove) { onMove(detail);
this.onMove(detail);
} }
} }
private calcGestureData(ev: UIEvent) { function calcGestureData(ev: UIEvent) {
const detail = this.detail; const prevX = detail.currentX;
const prevY = detail.currentY;
const prevT = detail.timeStamp;
updateDetail(ev, detail); updateDetail(ev, detail);
const currentX = detail.currentX; const currentX = detail.currentX;
const currentY = detail.currentY; const currentY = detail.currentY;
const timestamp = detail.timeStamp = now(ev); const timestamp = detail.timeStamp = now(ev);
const timeDelta = timestamp - prevT;
if (timeDelta > 0 && timeDelta < 100) {
const velocityX = (currentX - prevX) / timeDelta;
const velocityY = (currentY - prevY) / timeDelta;
detail.velocityX = velocityX * 0.7 + detail.velocityX * 0.3;
detail.velocityY = velocityY * 0.7 + detail.velocityY * 0.3;
}
detail.deltaX = currentX - detail.startX; detail.deltaX = currentX - detail.startX;
detail.deltaY = currentY - detail.startY; detail.deltaY = currentY - detail.startY;
detail.event = ev; detail.event = ev;
const timeRange = timestamp - 100;
const positions = this.positions;
let startPos = positions.length - 1;
// move pointer to position measured 100ms ago
while (startPos > 0 && positions[startPos] > timeRange) {
startPos -= 3;
}
if (startPos > 1) {
// compute relative movement between these two points
const frequency = 1 / (positions[startPos] - timestamp);
const movedY = positions[startPos - 1] - currentY;
const movedX = positions[startPos - 2] - currentX;
// based on XXms compute the movement to apply for each render step
// velocity = space/time = s*(1/t) = s*frequency
detail.velocityX = movedX * frequency;
detail.velocityY = movedY * frequency;
} else {
detail.velocityX = 0;
detail.velocityY = 0;
}
positions.push(currentX, currentY, timestamp);
} }
private tryToCapturePan(): boolean { function tryToCapturePan(): boolean {
if (this.gesture && !this.gesture.capture()) { if (gesture && !gesture.capture()) {
return false; return false;
} }
this.hasCapturedPan = true; hasCapturedPan = true;
this.hasFiredStart = false; hasFiredStart = false;
// reset start position since the real user-land event starts here // reset start position since the real user-land event starts here
// If the pan detector threshold is big, not reseting the start position // If the pan detector threshold is big, not reseting the start position
@ -254,71 +210,77 @@ export class Gesture {
// the array of positions used to calculate the gesture velocity does not // the array of positions used to calculate the gesture velocity does not
// need to be cleaned, more points in the positions array always results in a // need to be cleaned, more points in the positions array always results in a
// more acurate value of the velocity. // more acurate value of the velocity.
const detail = this.detail;
detail.startX = detail.currentX; detail.startX = detail.currentX;
detail.startY = detail.currentY; detail.startY = detail.currentY;
detail.startTimeStamp = detail.timeStamp; detail.startTimeStamp = detail.timeStamp;
if (this.onWillStart) { if (onWillStart) {
this.onWillStart(this.detail).then(this.fireOnStart.bind(this)); onWillStart(detail).then(fireOnStart);
} else { } else {
this.fireOnStart(); fireOnStart();
} }
return true; return true;
} }
private fireOnStart() { function fireOnStart() {
if (this.onStart) { if (onStart) {
this.onStart(this.detail); onStart(detail);
} }
this.hasFiredStart = true; hasFiredStart = true;
} }
private abortGesture() { function abortGesture() {
this.reset(); reset();
this.pointerEvents.stop(); pointerEvents.stop();
if (this.notCaptured) { if (notCaptured) {
this.notCaptured(this.detail); notCaptured(detail);
} }
} }
private reset() { function reset() {
this.hasCapturedPan = false; hasCapturedPan = false;
this.hasStartedPan = false; hasStartedPan = false;
this.isMoveQueued = false; isMoveQueued = false;
this.hasFiredStart = true; hasFiredStart = true;
if (this.gesture) {
this.gesture.release(); gesture.release();
}
} }
// END ************************* // END *************************
private pointerUp(ev: UIEvent) { function pointerUp(ev: UIEvent) {
const hasCaptured = this.hasCapturedPan; const tmpHasCaptured = hasCapturedPan;
const hasFiredStart = this.hasFiredStart; const tmpHasFiredStart = hasFiredStart;
this.reset(); reset();
if (!hasFiredStart) { if (!tmpHasFiredStart) {
return; return;
} }
this.calcGestureData(ev); calcGestureData(ev);
const detail = this.detail;
// Try to capture press // Try to capture press
if (hasCaptured) { if (tmpHasCaptured) {
if (this.onEnd) { if (onEnd) {
this.onEnd(detail); onEnd(detail);
} }
return; return;
} }
// Not captured any event // Not captured any event
if (this.notCaptured) { if (notCaptured) {
this.notCaptured(detail); notCaptured(detail);
} }
} }
return {
setDisabled(disabled: boolean) {
pointerEvents.disabled = disabled;
},
destroy() {
gesture.destroy();
pointerEvents.destroy();
}
};
} }
function updateDetail(ev: any, detail: GestureDetail) { function updateDetail(ev: any, detail: GestureDetail) {